diff --git a/configs/python/backend/backend.yaml b/configs/python/backend/backend.yaml index cf474bfa..714eb288 100644 --- a/configs/python/backend/backend.yaml +++ b/configs/python/backend/backend.yaml @@ -503,6 +503,11 @@ scanners: priority: 5 options: limit: 1000 + limit_metadata: True + size_limit: 250000000 + crack_pws: False + log_pws: True + password_file: '/etc/strelka/passwords.dat' 'ScanRpm': - positive: flavors: diff --git a/docs/README.md b/docs/README.md index ae440b6b..3a3c3395 100644 --- a/docs/README.md +++ b/docs/README.md @@ -789,7 +789,7 @@ The table below describes each scanner and its options. Each scanner has the hid | ScanFalconSandbox | Sends files to an instance of Falcon Sandbox | `server` -- URL of the Falcon Sandbox API inteface
`priority` -- Falcon Sandbox priority assigned to the task (defaults to `3`)
`timeout` -- amount of time (in seconds) to wait for the task to upload (defaults to `60`)
`envID` -- list of numeric envrionment IDs that tells Falcon Sandbox which sandbox to submit a sample to (defaults to `[100]`)
`api_key` -- API key used for authenticating to Falcon Sandbox (defaults to None, optionally read from environment variable "FS_API_KEY")
`api_secret` -- API secret key used for authenticating to Falcon Sandbox (defaults to None, optionally read from environment variable "FS_API_SECKEY") | | ScanFooter | Collects file footer | `length` -- number of footer characters to log as metadata (defaults to `50`)
`encodings` -- list of output encodings, any of `classic`, `raw`, `hex`, `backslash` | | ScanGif | Extracts data embedded in GIF files | N/A | -| ScanGzip | Decompresses gzip files | N/A +| ScanGzip | Decompresses gzip files | N/A | ScanHash | Calculates file hash values | N/A | | ScanHeader | Collects file header | `length` -- number of header characters to log as metadata (defaults to `50`)
`encodings` -- list of output encodings, any of `classic`, `raw`, `hex`, `backslash` | | ScanHtml | Collects metadata and extracts embedded files from HTML files | `parser` -- sets the HTML parser used during scanning (defaults to `html.parser`)
`max_links` -- Maximum amount of links to output in hyperlinks field (defaults to `50`) | @@ -816,7 +816,7 @@ The table below describes each scanner and its options. Each scanner has the hid | ScanPkcs7 | Extracts files from PKCS7 certificate files | N/A | | ScanPlist | Collects attributes from binary and XML property list files | `keys` -- list of keys to log (defaults to `all`) | | ScanQr | Collects QR code metadata from image files | `support_inverted` -- Enable/disable image inversion to support inverted QR codes (white on black). Adds some image processing overhead. | [Aaron Herman](https://github.com/aaronherman) -| ScanRar | Extracts files from RAR archives | `limit` -- maximum number of files to extract (defaults to `1000`)
`password_file` -- location of passwords file for RAR archives (defaults to `/etc/strelka/passwords.dat`) | +| ScanRar | Extracts files from RAR archives | `limit` -- maximum number of files to extract (defaults to `1000`)
`limit_metadata` -- stop adding file metadata when `limit` is reached (defaults to true)
`size_limit` -- maximum size for extracted files (defaults to `250000000`)
`crack_pws` -- use a dictionary to crack encrypted files (defaults to false)
`log_pws` -- log cracked passwords (defaults to true)
`password_file` -- location of passwords file for rar archives (defaults to `/etc/strelka/passwords.dat`) | | ScanRpm | Collects metadata and extracts files from RPM files | `tempfile_directory` -- location where `tempfile` will write temporary files (defaults to `/tmp/`) | | ScanRtf | Extracts embedded files from RTF files | `limit` -- maximum number of files to extract (defaults to `1000`) | | ScanSave | Exposes raw file data in the output response in an encoded and compressed format | `compression` -- compression algorithm to use on the raw file data (defaults to `gzip` - `bzip2`, `lzma`, and `none` are available)
`encoding` -- JSON compatible encoding algorithm to use on the raw file data (defaults to `base64` - `base85` also available) | [Kevin Eiche](https://github.com/keiche) @@ -839,7 +839,7 @@ The table below describes each scanner and its options. Each scanner has the hid | ScanXml | Log metadata and extract files from XML files | `extract_tags` -- list of XML tags that will have their text extracted as child files (defaults to empty list)
`metadata_tags` -- list of XML tags that will have their text logged as metadata (defaults to empty list) | | ScanYara | Scans files with YARA rules | `location` -- location of the YARA rules file or directory (defaults to `/etc/strelka/yara/`)
`compiled` -- Enable use of compiled YARA rules, as well as the path.
`store_offset` -- Stores file offset for YARA match
`offset_meta_key` -- YARA meta key that must exist in the YARA rule for the offset to be stored.
`offset_padding` -- Amount of data to be stored before and after offset for additional context.
`category_key` -- Metadata key used to extract categories for YARA matches.
`categories` -- List of categories to organize YARA rules, which can be individually toggled to show metadata.
`show_meta` -- Toggles whether to show metadata for matches in each category.
`meta_fields` -- Specifies which metadata fields should be extracted for display.
`show_all_meta` -- Displays all metadata for each YARA rule match when enabled. | | ScanZip | Extracts files from zip archives | `limit` -- maximum number of files to extract (defaults to `1000`)
`limit_metadata` -- stop adding file metadata when `limit` is reached (defaults to true)
`size_limit` -- maximum size for extracted files (defaults to `250000000`)
`crack_pws` -- use a dictionary to crack encrypted files (defaults to false)
`log_pws` -- log cracked passwords (defaults to true)
`password_file` -- location of passwords file for zip archives (defaults to `/etc/strelka/passwords.dat`) | -| ScanZlib | Decompresses gzip files | N/A +| ScanZlib | Decompresses gzip files | N/A ## Tests As Strelka consists of many scanners and dependencies for those scanners. Pytests are particularly valuable for testing the ongoing functionality of Strelka and it's scanners. Tests allow users to write test cases that verify the correct behavior of Strelka scanners to ensure that the scanners remain reliable and accurate. Additionally, using pytests can help streamline the development process, allowing developers to focus on writing new features and improvements for the scanners. Strelka contains a set of standard test fixture files that represent the types of files Strelka ingests. Test fixtures can also be loaded remotely with the helper functions `get_remote_fixture` and `get_remote_fixture_archive` for scanner tests that need malicious samples. diff --git a/src/python/strelka/scanners/scan_rar.py b/src/python/strelka/scanners/scan_rar.py index 51717e5f..245e99ee 100644 --- a/src/python/strelka/scanners/scan_rar.py +++ b/src/python/strelka/scanners/scan_rar.py @@ -35,103 +35,186 @@ def init(self): self.passwords = [] def scan(self, data, file, options, expire_at): - file_limit = options.get("limit", 1000) + file_limit = options.get("limit", 100) + size_limit = options.get("size_limit", 250000000) + limit_metadata = options.get("limit_metadata", True) + crack_pws = options.get("crack_pws", False) + log_pws = options.get("log_pws", True) password_file = options.get("password_file", "/etc/strelka/passwords.dat") + # Gather count and list of files to be extracted self.event["total"] = {"files": 0, "extracted": 0} - - if not self.passwords: - if os.path.isfile(password_file): - with open(password_file, "rb") as f: - for line in f: - self.passwords.append(line.strip()) + self.event["files"] = [] + + # Temporary top level compression metrics + compress_size_total = 0 + file_size_total = 0 + + if crack_pws: + if not self.passwords: + if os.path.isfile(password_file): + with open(password_file, "rb") as f: + for line in f: + self.passwords.append(line.strip()) + + if ( + len(self.passwords) == 0 + and "no_passwords_loaded" not in self.flags + ): + self.flags.append("no_passwords_loaded") + else: + if "password_file_missing" not in self.flags: + self.flags.append("password_file_missing") + + self.passwords.insert(0, None) with io.BytesIO(data) as rar_io: try: with rarfile.RarFile(rar_io) as rar_obj: - rf_info_list = rar_obj.infolist() - for info in rf_info_list: - if info.is_file(): - self.event["total"]["files"] += 1 + filelist = rar_obj.infolist() + + # Count the file entries, in case the function encounters an unhandled exception + for compressed_file in filelist: + if compressed_file.is_dir(): + continue + self.event["total"]["files"] += 1 - password = "" - for i, name in enumerate(rf_info_list): + # For each file in rar, gather metadata and pass extracted file back to Strelka + for i, name in enumerate(filelist): if not name.isdir(): - if self.event["total"]["extracted"] >= file_limit: - break + + extract = True + extracted = False + compression_rate = 0 try: extract_data = b"" - file_info = rar_obj.getinfo(name) + compressed_file = rar_obj.getinfo(name) + + if compressed_file.file_size > size_limit: + extract = False + if "file_size_limit" not in self.flags: + self.flags.append("file_size_limit") + + if self.event["total"]["extracted"] >= file_limit: + extract = False + if "file_count_limit" not in self.flags: + self.flags.append("file_count_limit") + + if ( + compressed_file.file_size > 0 + and compressed_file.compress_size > 0 + ): + compress_size_total += compressed_file.compress_size + file_size_total += compressed_file.file_size + + size_difference = ( + compressed_file.file_size + - compressed_file.compress_size + ) + compression_rate = ( + size_difference * 100.0 + ) / compressed_file.file_size + self.event["host_os"] = HOST_OS_MAPPING[ - file_info.host_os + compressed_file.host_os ] - if not file_info.needs_password(): + if not compressed_file.needs_password(): extract_data = rar_obj.read(name) else: - if i == 0: + if "password_protected" not in self.flags: self.flags.append("password_protected") - if not password and i == 0: - for pw in self.passwords: - try: - data = rar_obj.open( - name, - mode="r", - psw=pw.decode("utf-8"), - ) - if data.readable(): - extract_data = data.readall() - password = pw.decode("utf-8") - self.event["password"] = pw.decode( - "utf-8" - ) - break - except ( - RuntimeError, - rarfile.BadRarFile, - rarfile.RarCRCError, - rarfile.RarWrongPassword, - ): - pass - elif not password and i > 0: - break - else: + for password in self.passwords: try: data = rar_obj.open( - name, mode="r", psw=password + name, + mode="r", + pwd=password.decode("utf-8") + if password + else None, ) if data.readable(): extract_data = data.readall() + self.passwords.insert( + 0, + self.passwords.pop( + self.passwords.index(password) + ), + ) + if password and log_pws: + self.event[ + "password" + ] = password.decode("utf-8") + break except ( RuntimeError, - rarfile.BadRarFile, rarfile.RarCRCError, rarfile.RarWrongPassword, + ): + raise + except ( + rarfile.BadRarFile, + rarfile.PasswordRequired, ): pass if ( not extract_data and "no_password_match_found" not in self.flags + and not crack_pws ): self.flags.append("no_password_match_found") - if extract_data: + # If there's data in it, and no limits have been met, emit the file + if extract_data and extract: # Send extracted file back to Strelka self.emit_file( - extract_data, name=f"{file_info.filename}" + extract_data, name=f"{compressed_file.filename}" ) + extracted = True + + if not ( + limit_metadata + and self.event["total"]["extracted"] >= file_limit + ): + self.event["files"].append( + { + "file_name": compressed_file.filename, + "file_size": compressed_file.file_size, + "compression_size": compressed_file.compress_size, + "compression_rate": round( + compression_rate, 2 + ), + "extracted": extracted, + "encrypted": compressed_file.needs_password(), + } + ) + + if extracted: self.event["total"]["extracted"] += 1 except NotImplementedError: self.flags.append("unsupport_compression") + raise except RuntimeError: self.flags.append("runtime_error") + raise except ValueError: self.flags.append("value_error") + raise except rarfile.BadRarFile: + raise self.flags.append("bad_rar") + + # Top level compression metric + if file_size_total > 0 and compress_size_total > 0: + size_difference_total = file_size_total - compress_size_total + self.event["compression_rate"] = round( + (size_difference_total * 100.0) / file_size_total, 2 + ) + else: + self.event["compression_rate"] = 0.00 diff --git a/src/python/strelka/scanners/scan_zip.py b/src/python/strelka/scanners/scan_zip.py index a5297a0d..d1e3c44c 100644 --- a/src/python/strelka/scanners/scan_zip.py +++ b/src/python/strelka/scanners/scan_zip.py @@ -20,6 +20,9 @@ class ScanZip(strelka.Scanner): Defaults to /etc/strelka/passwords.dat. """ + def init(self): + self.passwords = [] + def scan(self, data, file, options, expire_at): file_limit = options.get("limit", 100) size_limit = options.get("size_limit", 250000000) @@ -28,8 +31,6 @@ def scan(self, data, file, options, expire_at): log_pws = options.get("log_pws", True) password_file = options.get("password_file", "/etc/strelka/passwords.dat") - passwords = [None] - # Gather count and list of files to be extracted self.event["total"] = {"files": 0, "extracted": 0} self.event["files"] = [] @@ -38,10 +39,23 @@ def scan(self, data, file, options, expire_at): compress_size_total = 0 file_size_total = 0 - if crack_pws and os.path.isfile(password_file): - with open(password_file, "rb") as f: - for line in f: - passwords.append(line.strip()) + if crack_pws: + if not self.passwords: + if os.path.isfile(password_file): + with open(password_file, "rb") as f: + for line in f: + self.passwords.append(line.strip()) + + if ( + len(self.passwords) == 0 + and "no_passwords_loaded" not in self.flags + ): + self.flags.append("no_passwords_loaded") + else: + if "password_file_missing" not in self.flags: + self.flags.append("password_file_missing") + + self.passwords.insert(0, None) with io.BytesIO(data) as zip_io: try: @@ -96,17 +110,17 @@ def scan(self, data, file, options, expire_at): if "encrypted" not in self.flags: self.flags.append("encrypted") - for password in passwords: + for password in self.passwords: try: if extract: extract_data = zip_obj.read( compressed_file.filename, password ) if extract_data: - passwords.insert( + self.passwords.insert( 0, - passwords.pop( - passwords.index(password) + self.passwords.pop( + self.passwords.index(password) ), ) if password and crack_pws and log_pws: diff --git a/src/python/strelka/tests/fixtures/test_big.rar b/src/python/strelka/tests/fixtures/test_big.rar new file mode 100755 index 00000000..d6aeeae5 Binary files /dev/null and b/src/python/strelka/tests/fixtures/test_big.rar differ diff --git a/src/python/strelka/tests/fixtures/test_password.rar b/src/python/strelka/tests/fixtures/test_password.rar new file mode 100755 index 00000000..ba7daf75 Binary files /dev/null and b/src/python/strelka/tests/fixtures/test_password.rar differ diff --git a/src/python/strelka/tests/test_scan_rar.py b/src/python/strelka/tests/test_scan_rar.py index 1be94e30..fb5949b6 100644 --- a/src/python/strelka/tests/test_scan_rar.py +++ b/src/python/strelka/tests/test_scan_rar.py @@ -16,6 +16,33 @@ def test_scan_rar(mocker): "flags": [], "total": {"files": 3, "extracted": 3}, "host_os": "RAR_OS_WIN32", + "files": [ + { + "file_name": "hidden/lorem-hidden.txt", + "file_size": 4015, + "compression_size": 1484, + "compression_rate": 63.04, + "extracted": True, + "encrypted": False, + }, + { + "file_name": "hidden/lorem-readonly.txt", + "file_size": 4015, + "compression_size": 1484, + "compression_rate": 63.04, + "extracted": True, + "encrypted": False, + }, + { + "file_name": "lorem.txt", + "file_size": 4015, + "compression_size": 1484, + "compression_rate": 63.04, + "extracted": True, + "encrypted": False, + }, + ], + "compression_rate": 63.04, } scanner_event = run_test_scan( @@ -26,3 +53,494 @@ def test_scan_rar(mocker): TestCase.maxDiff = None TestCase().assertDictEqual(test_scan_event, scanner_event) + + +def test_scan_rar_file_limit(mocker): + """ + Pass: Sample event matches output of scanner. + Failure: Unable to load file or sample event fails to match. + """ + + test_scan_event = { + "elapsed": mock.ANY, + "flags": ["file_count_limit"], + "total": {"files": 3, "extracted": 1}, + "host_os": "RAR_OS_WIN32", + "files": [ + { + "file_name": "hidden/lorem-hidden.txt", + "file_size": 4015, + "compression_size": 1484, + "compression_rate": 63.04, + "extracted": True, + "encrypted": False, + }, + { + "file_name": "hidden/lorem-readonly.txt", + "file_size": 4015, + "compression_size": 1484, + "compression_rate": 63.04, + "extracted": False, + "encrypted": False, + }, + { + "file_name": "lorem.txt", + "file_size": 4015, + "compression_size": 1484, + "compression_rate": 63.04, + "extracted": False, + "encrypted": False, + }, + ], + "compression_rate": 63.04, + } + + scanner_event = run_test_scan( + mocker=mocker, + scan_class=ScanUnderTest, + fixture_path=Path(__file__).parent / "fixtures/test.rar", + options={ + "limit": 1, + "limit_metadata": False, + "size_limit": 250000000, + "crack_pws": True, + "log_pws": True, + "password_file": "/etc/strelka/passwords.dat", + }, + ) + + TestCase.maxDiff = None + TestCase().assertDictEqual(test_scan_event, scanner_event) + + +def test_scan_rar_file_limit_no_meta(mocker): + """ + Pass: Sample event matches output of scanner. + Failure: Unable to load file or sample event fails to match. + """ + + test_scan_event = { + "elapsed": mock.ANY, + "flags": ["file_count_limit"], + "total": {"files": 3, "extracted": 1}, + "host_os": "RAR_OS_WIN32", + "files": [ + { + "file_name": "hidden/lorem-hidden.txt", + "file_size": 4015, + "compression_size": 1484, + "compression_rate": 63.04, + "extracted": True, + "encrypted": False, + }, + ], + "compression_rate": 63.04, + } + + scanner_event = run_test_scan( + mocker=mocker, + scan_class=ScanUnderTest, + fixture_path=Path(__file__).parent / "fixtures/test.rar", + options={ + "limit": 1, + "limit_metadata": True, + "size_limit": 250000000, + "crack_pws": True, + "log_pws": True, + "password_file": "/etc/strelka/passwords.dat", + }, + ) + + TestCase.maxDiff = None + TestCase().assertDictEqual(test_scan_event, scanner_event) + + +def test_scan_rar_crack_pws_unencrypted(mocker): + """ + Pass: Sample event matches output of scanner. + Failure: Unable to load file or sample event fails to match. + """ + + test_scan_event = { + "elapsed": mock.ANY, + "flags": [], + "total": {"files": 3, "extracted": 3}, + "host_os": "RAR_OS_WIN32", + "files": [ + { + "file_name": "hidden/lorem-hidden.txt", + "file_size": 4015, + "compression_size": 1484, + "compression_rate": 63.04, + "extracted": True, + "encrypted": False, + }, + { + "file_name": "hidden/lorem-readonly.txt", + "file_size": 4015, + "compression_size": 1484, + "compression_rate": 63.04, + "extracted": True, + "encrypted": False, + }, + { + "file_name": "lorem.txt", + "file_size": 4015, + "compression_size": 1484, + "compression_rate": 63.04, + "extracted": True, + "encrypted": False, + }, + ], + "compression_rate": 63.04, + } + + scanner_event = run_test_scan( + mocker=mocker, + scan_class=ScanUnderTest, + fixture_path=Path(__file__).parent / "fixtures/test.rar", + options={ + "limit": 1000, + "limit_metadata": True, + "size_limit": 250000000, + "crack_pws": True, + "log_pws": True, + "password_file": "/etc/strelka/passwords.dat", + }, + ) + + TestCase.maxDiff = None + TestCase().assertDictEqual(test_scan_event, scanner_event) + + +def test_scan_rar_password(mocker): + """ + Pass: Sample event matches output of scanner. + Failure: Unable to load file or sample event fails to match. + """ + + test_scan_event = { + "elapsed": mock.ANY, + "flags": ["password_protected"], + "total": {"files": 3, "extracted": 3}, + "host_os": "RAR_OS_WIN32", + "files": [ + { + "file_name": "hidden/lorem-hidden.txt", + "file_size": 4015, + "compression_size": 1488, + "compression_rate": 62.94, + "extracted": True, + "encrypted": True, + }, + { + "file_name": "hidden/lorem-readonly.txt", + "file_size": 4015, + "compression_size": 1488, + "compression_rate": 62.94, + "extracted": True, + "encrypted": True, + }, + { + "file_name": "lorem.txt", + "file_size": 4015, + "compression_size": 1488, + "compression_rate": 62.94, + "extracted": True, + "encrypted": True, + }, + ], + "compression_rate": 62.94, + } + + scanner_event = run_test_scan( + mocker=mocker, + scan_class=ScanUnderTest, + fixture_path=Path(__file__).parent / "fixtures/test_password.rar", + options={ + "limit": 1000, + "limit_metadata": True, + "size_limit": 250000000, + "crack_pws": True, + "log_pws": False, + "password_file": "/etc/strelka/passwords.dat", + }, + ) + + TestCase.maxDiff = None + TestCase().assertDictEqual(test_scan_event, scanner_event) + + +def test_scan_rar_password_log_pwd(mocker): + """ + Pass: Sample event matches output of scanner. + Failure: Unable to load file or sample event fails to match. + """ + + test_scan_event = { + "elapsed": mock.ANY, + "flags": ["password_protected"], + "password": "password", + "total": {"files": 3, "extracted": 3}, + "host_os": "RAR_OS_WIN32", + "files": [ + { + "file_name": "hidden/lorem-hidden.txt", + "file_size": 4015, + "compression_size": 1488, + "compression_rate": 62.94, + "extracted": True, + "encrypted": True, + }, + { + "file_name": "hidden/lorem-readonly.txt", + "file_size": 4015, + "compression_size": 1488, + "compression_rate": 62.94, + "extracted": True, + "encrypted": True, + }, + { + "file_name": "lorem.txt", + "file_size": 4015, + "compression_size": 1488, + "compression_rate": 62.94, + "extracted": True, + "encrypted": True, + }, + ], + "compression_rate": 62.94, + } + + scanner_event = run_test_scan( + mocker=mocker, + scan_class=ScanUnderTest, + fixture_path=Path(__file__).parent / "fixtures/test_password.rar", + options={ + "limit": 1000, + "limit_metadata": True, + "size_limit": 250000000, + "crack_pws": True, + "log_pws": True, + "password_file": "/etc/strelka/passwords.dat", + }, + ) + + TestCase.maxDiff = None + TestCase().assertDictEqual(test_scan_event, scanner_event) + + +def test_scan_rar_password_crack_pws(mocker): + """ + Pass: Sample event matches output of scanner. + Failure: Unable to load file or sample event fails to match. + """ + + test_scan_event = { + "elapsed": mock.ANY, + "flags": ["password_protected", "no_password_match_found"], + "total": {"files": 3, "extracted": 0}, + "host_os": "RAR_OS_WIN32", + "files": [ + { + "file_name": "hidden/lorem-hidden.txt", + "file_size": 4015, + "compression_size": 1488, + "compression_rate": 62.94, + "extracted": False, + "encrypted": True, + }, + { + "file_name": "hidden/lorem-readonly.txt", + "file_size": 4015, + "compression_size": 1488, + "compression_rate": 62.94, + "extracted": False, + "encrypted": True, + }, + { + "file_name": "lorem.txt", + "file_size": 4015, + "compression_size": 1488, + "compression_rate": 62.94, + "extracted": False, + "encrypted": True, + }, + ], + "compression_rate": 62.94, + } + + scanner_event = run_test_scan( + mocker=mocker, + scan_class=ScanUnderTest, + fixture_path=Path(__file__).parent / "fixtures/test_password.rar", + options={ + "limit": 1000, + "limit_metadata": True, + "size_limit": 250000000, + "crack_pws": False, + "log_pws": True, + "password_file": "/etc/strelka/passwords.dat", + }, + ) + + TestCase.maxDiff = None + TestCase().assertDictEqual(test_scan_event, scanner_event) + + +def test_scan_rar_password_bad_path(mocker): + """ + Pass: Sample event matches output of scanner. + Failure: Unable to load file or sample event fails to match. + """ + + test_scan_event = { + "elapsed": mock.ANY, + "flags": ["password_file_missing", "password_protected"], + "total": {"files": 3, "extracted": 0}, + "host_os": "RAR_OS_WIN32", + "files": [ + { + "file_name": "hidden/lorem-hidden.txt", + "file_size": 4015, + "compression_size": 1488, + "compression_rate": 62.94, + "extracted": False, + "encrypted": True, + }, + { + "file_name": "hidden/lorem-readonly.txt", + "file_size": 4015, + "compression_size": 1488, + "compression_rate": 62.94, + "extracted": False, + "encrypted": True, + }, + { + "file_name": "lorem.txt", + "file_size": 4015, + "compression_size": 1488, + "compression_rate": 62.94, + "extracted": False, + "encrypted": True, + }, + ], + "compression_rate": 62.94, + } + + scanner_event = run_test_scan( + mocker=mocker, + scan_class=ScanUnderTest, + fixture_path=Path(__file__).parent / "fixtures/test_password.rar", + options={ + "limit": 1000, + "limit_metadata": True, + "size_limit": 250000000, + "crack_pws": True, + "log_pws": True, + "password_file": "/etc/strelka/nosuchfile", + }, + ) + + TestCase.maxDiff = None + TestCase().assertDictEqual(test_scan_event, scanner_event) + + +def test_scan_rar_password_empty_file(mocker): + """ + Pass: Sample event matches output of scanner. + Failure: Unable to load file or sample event fails to match. + """ + + test_scan_event = { + "elapsed": mock.ANY, + "flags": ["no_passwords_loaded", "password_protected"], + "total": {"files": 3, "extracted": 0}, + "host_os": "RAR_OS_WIN32", + "files": [ + { + "file_name": "hidden/lorem-hidden.txt", + "file_size": 4015, + "compression_size": 1488, + "compression_rate": 62.94, + "extracted": False, + "encrypted": True, + }, + { + "file_name": "hidden/lorem-readonly.txt", + "file_size": 4015, + "compression_size": 1488, + "compression_rate": 62.94, + "extracted": False, + "encrypted": True, + }, + { + "file_name": "lorem.txt", + "file_size": 4015, + "compression_size": 1488, + "compression_rate": 62.94, + "extracted": False, + "encrypted": True, + }, + ], + "compression_rate": 62.94, + } + + scanner_event = run_test_scan( + mocker=mocker, + scan_class=ScanUnderTest, + fixture_path=Path(__file__).parent / "fixtures/test_password.rar", + options={ + "limit": 1000, + "limit_metadata": True, + "size_limit": 250000000, + "crack_pws": True, + "log_pws": True, + "password_file": Path(__file__).parent / "fixtures/test.empty", + }, + ) + + TestCase.maxDiff = None + TestCase().assertDictEqual(test_scan_event, scanner_event) + + +def test_scan_rar_big(mocker): + """ + Pass: Sample event matches output of scanner. + Failure: Unable to load file or sample event fails to match. + """ + + test_scan_event = { + "elapsed": mock.ANY, + "flags": ["file_size_limit"], + "total": {"files": 1, "extracted": 0}, + "host_os": "RAR_OS_WIN32", + "files": [ + { + "file_name": "test_big.zero", + "file_size": 512000000, + "compression_size": 20674, + "compression_rate": 100.0, + "extracted": False, + "encrypted": False, + }, + ], + "compression_rate": 100.0, + } + + scanner_event = run_test_scan( + mocker=mocker, + scan_class=ScanUnderTest, + fixture_path=Path(__file__).parent / "fixtures/test_big.rar", + options={ + "limit": 1000, + "limit_metadata": True, + "size_limit": 511999999, + "crack_pws": True, + "log_pws": True, + "password_file": "/etc/strelka/passwords.dat", + }, + ) + + TestCase.maxDiff = None + TestCase().assertDictEqual(test_scan_event, scanner_event)