nabla-c0d3 / sslyze

Fast and powerful SSL/TLS scanning library.
GNU Affero General Public License v3.0
3.28k stars 454 forks source link

pem_files path is incorrect when using PyInstaller's onefile bundling #607

Closed TechSupportJosh closed 1 year ago

TechSupportJosh commented 1 year ago

Describe the bug When using PyInstaller's onefile bundling method, and adding pem_files to the bundle, sslyze will attempt to load pem_files from where the bundled executable is executed from, rather than searching in the temporary directory created in /tmp/_MEI....

From what I can tell, this is because sys.executable when using onefile bundling points to the binary that the user called e.g. /home/user/mytool. The function responsible for this doesn't utilise sys._MEIPASS (the directory which points towards the bundle folder, regardless of whether it was one-folder or one-file bundled):

https://github.com/nabla-c0d3/sslyze/blob/fccf7f9dd49178d0dae5e41599d4e13de64896d3/sslyze/plugins/certificate_info/trust_stores/trust_store_repository.py#L29-L37

To Reproduce Steps to reproduce the behavior:

  1. Install sslyze and pyinstaller
    pip install sslyze pyinstaller
  2. Create a file test.py with the following content:
    
    from datetime import datetime
    from sslyze import (
    ServerScanRequest,
    ServerNetworkLocation,
    Scanner,
    ServerHostnameCouldNotBeResolved,
    ServerScanStatusEnum,
    ScanCommandAttemptStatusEnum,
    )
    import sys

print("sys.executable:", sys.executable) print("sys._MEIPASS", sys._MEIPASS if hasattr(sys, "_MEIPASS") else "N/A") print()

print("=> Starting the scans") date_scans_started = datetime.utcnow()

First create the scan requests for each server that we want to scan

try: all_scan_requests = [ ServerScanRequest( server_location=ServerNetworkLocation(hostname="cloudflare.com") ), ServerScanRequest(server_location=ServerNetworkLocation(hostname="google.com")), ] except ServerHostnameCouldNotBeResolved:

Handle bad input ie. invalid hostnames

print("Error resolving the supplied hostnames")
exit()

Then queue all the scans

scanner = Scanner() scanner.queue_scans(all_scan_requests)

And retrieve and process the results for each server

all_server_scan_results = [] for server_scan_result in scanner.get_results(): all_server_scan_results.append(server_scan_result) print(f"\n\nResults for {server_scan_result.server_location.hostname}")

# Were we able to connect to the server and run the scan?
if server_scan_result.scan_status == ServerScanStatusEnum.ERROR_NO_CONNECTIVITY:
    # No we weren't
    print(
        f"\nError: Could not connect to {server_scan_result.server_location.hostname}:"
        f" {server_scan_result.connectivity_error_trace}"
    )
    continue

# Since we were able to run the scan, scan_result is populated
assert server_scan_result.scan_result

# Process the result of the certificate info scan command
certinfo_attempt = server_scan_result.scan_result.certificate_info
if certinfo_attempt.status == ScanCommandAttemptStatusEnum.ERROR:
    print(certinfo_attempt.status)
    print(certinfo_attempt.error_reason)
    print(certinfo_attempt.error_trace)
elif certinfo_attempt.status == ScanCommandAttemptStatusEnum.COMPLETED:
    certinfo_result = certinfo_attempt.result
    assert certinfo_result
    print("Certificate worked!")
3. Build with pyinstaller (specifying the path to the sslyze's pem_stores folder, here I installed into a venv, your path may vary)

pyinstaller --add-data "./venv/lib/python3.10/site-packages/sslyze/plugins/certificate_info/trust_stores/pem_files:pem_files" --onefile ./test.py

4. Run command and verify that `sys.executable` points to the binary in the `dist` folder, rather than the `/tmp` folder created by PyInstaller:

$ ./test sys.executable: /home/josh/sslyze_bug_fix/dist/test sys._MEIPASS /tmp/_MEIqVkqr7

=> Starting the scans

Results for google.com ScanCommandAttemptStatusEnum.ERROR ScanCommandErrorReasonEnum.BUG_IN_SSLYZE [Errno 2] No such file or directory: '/home/josh/sslyze_bug_fix/dist/pem_files/apple.yaml'

Results for cloudflare.com ScanCommandAttemptStatusEnum.ERROR ScanCommandErrorReasonEnum.BUG_IN_SSLYZE [Errno 2] No such file or directory: '/home/josh/sslyze_bug_fix/dist/pem_files/apple.yaml'



**Expected behavior**
If the application detects `_MEIPASS`, it should use this directory rather than the `sys.executable` parent directory.

**Python environment:**
 - OS: Ubuntu 22.04.2 LTS
 - Python version: 3.10
TechSupportJosh commented 1 year ago

Adding this to the top of _get_script_dir seems to fix it, but would be nice if this could be included within the library:

    if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"):
        return Path(sys._MEIPASS)
nabla-c0d3 commented 1 year ago

Hello, Unfortunately, only cx_freeze on Windows is supported at the moment, and I have no plans to add support for pyInstaller. Good luck tho!