From 6381b67125cd18da4f764d68a4ba392b68be2e69 Mon Sep 17 00:00:00 2001
From: Ryan O'Horo <10855297+ryanohoro@users.noreply.github.com>
Date: Wed, 6 Mar 2024 23:23:05 -0600
Subject: [PATCH] Port ScanZip improvements to ScanRar, fixes #441, port
password caching to ScanZip
---
configs/python/backend/backend.yaml | 5 +
docs/README.md | 6 +-
src/python/strelka/scanners/scan_rar.py | 179 ++++--
src/python/strelka/scanners/scan_zip.py | 34 +-
.../strelka/tests/fixtures/test_big.rar | Bin 0 -> 20848 bytes
.../strelka/tests/fixtures/test_password.rar | Bin 0 -> 4832 bytes
src/python/strelka/tests/test_scan_rar.py | 518 ++++++++++++++++++
7 files changed, 681 insertions(+), 61 deletions(-)
create mode 100755 src/python/strelka/tests/fixtures/test_big.rar
create mode 100755 src/python/strelka/tests/fixtures/test_password.rar
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 0000000000000000000000000000000000000000..d6aeeae5ee2a6face12c377cd4fd969900243494
GIT binary patch
literal 20848
zcmeI4zbk}s9LGQ3=W)~>P6kT(wOABMS%kr2;|#Kik}T*bW$`r`B;`LiWsm`@#ia~L
z-KH#_GRVYWxPd$G^A9NLvAnLQ&t2bcdiQ>R-mm5PPR?dKTWXv&o)yXsE>UYK=XMXa
zeC1<@L3d+c5T-73I01BYM|0rO4)7wc`ZZBPRypB}RG+jZ}@EcvrUOzcR5ZS;mLnIYQ2b;mm2W&;Q
zqHMrcBnSlIuYv%I0$GvF1Y`kOUOu2(=oZ-k$RY>?fgr@$2%sn^8p&{=C@AXVTs%}g
zRWHB6yaa(D5Ckazih`n%d;p4qqFz3r7%4{C074`P1c4yL*$AL0C>qIdpeQKn<6Jyc
zJykEi!Mp^4AP@v80E&X5k$eD(f}&nNpcpAe*#JT$2n2y3#Mubf!^hRUK~DpNqM)dc
zbMa91RQ*p?U-Ka(ZhzB-E}Qvu(di1prJK{K`mj20rpN1A!pyk4czPbRV$Kcx?o!0o
K^{GslsIo5~q87CP
literal 0
HcmV?d00001
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 0000000000000000000000000000000000000000..ba7daf75afbb2146730292c3d53ffa44bec2ef08
GIT binary patch
literal 4832
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
literal 0
HcmV?d00001
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)