From ba61662d03e4d74e3298c0aeab6d2712e9b98ee5 Mon Sep 17 00:00:00 2001 From: Justin Bollinger Date: Thu, 5 Feb 2026 15:39:08 -0500 Subject: [PATCH] automatic hash_id detection via api for hashview hashes and updated tests. --- hate_crack/main.py | 85 +++++++++++++++---- ...est_hashview_cli_subcommands_subprocess.py | 18 +++- 2 files changed, 84 insertions(+), 19 deletions(-) diff --git a/hate_crack/main.py b/hate_crack/main.py index 7b45b7b..3add524 100755 --- a/hate_crack/main.py +++ b/hate_crack/main.py @@ -1815,18 +1815,47 @@ def hashview_api(): print("\n" + "=" * 60) print("What would you like to do?") print("=" * 60) - print("\t(1) Upload Cracked Hashes from current session") - print("\t(2) Upload Wordlist") - print("\t(3) Download Wordlist") - print("\t(4) Download Left Hashes (with automatic merge if found)") + + # Build dynamic menu based on state + menu_options = [] if hcatHashFile: - print("\t(5) Upload Hashfile and Create Job") - print("\t(99) Back to Main Menu") + menu_options.append(("upload_cracked", "Upload Cracked Hashes from current session")) + menu_options.append(("upload_wordlist", "Upload Wordlist")) + menu_options.append(("download_wordlist", "Download Wordlist")) + menu_options.append(("download_left", "Download Left Hashes (with automatic merge if found)")) + if hcatHashFile: + menu_options.append(("upload_hashfile_job", "Upload Hashfile and Create Job")) + menu_options.append(("back", "Back to Main Menu")) + + # Display menu with dynamic numbering + for i, (option_key, option_text) in enumerate(menu_options, 1): + if option_key == "back": + print(f"\t(99) {option_text}") + else: + print(f"\t({i}) {option_text}") + + # Create mapping of display numbers to option keys + option_map = {} + display_num = 1 + for option_key, _ in menu_options[:-1]: # All except "back" + option_map[str(display_num)] = option_key + display_num += 1 + option_map["99"] = "back" choice = input("\nSelect an option: ") + + if choice not in option_map: + print("Invalid option. Please try again.") + continue + + option_key = option_map[choice] - if choice == "1": + if option_key == "upload_cracked": # Upload cracked hashes + if not hcatHashFile: + print("\n✗ Error: No hashfile is currently set. This option is not available.") + continue + print("\n" + "-" * 60) print("Upload Cracked Hashes") print("-" * 60) @@ -1906,7 +1935,7 @@ def hashview_api(): print("\nFull error details:") traceback.print_exc() - elif choice == "2": + elif option_key == "upload_wordlist": print("\n" + "-" * 60) print("Upload Wordlist") print("-" * 60) @@ -1936,7 +1965,7 @@ def hashview_api(): except Exception as e: print(f"\n✗ Error uploading wordlist: {str(e)}") - elif choice == "3": + elif option_key == "download_wordlist": # Download wordlist try: wordlists = api_harness.list_wordlists() @@ -1982,7 +2011,7 @@ def hashview_api(): except Exception as e: print(f"\n✗ Error downloading wordlist: {str(e)}") - elif choice == "5": + elif option_key == "upload_hashfile_job": # Upload hashfile and create job if not hcatHashFile: print("\n✗ Error: No hashfile is currently set.") @@ -2154,7 +2183,7 @@ def hashview_api(): except Exception as e: print(f"\n✗ Error uploading hashfile: {str(e)}") - elif choice == "4": + elif option_key == "download_left": # Download left hashes try: while True: @@ -2300,13 +2329,11 @@ def hashview_api(): except Exception as e: print(f"\n✗ Error downloading hashes: {str(e)}") - elif choice == "99": + elif option_key == "back": break - else: - print("Invalid option. Please try again.") except KeyboardInterrupt: - print("\n\nHashview upload canceled.") + quit_hc() except Exception as e: print(f"\nError connecting to Hashview: {str(e)}") @@ -2698,6 +2725,17 @@ def main(): "--hash-type", default=None, help="Hash type for hashcat (e.g., 1000 for NTLM)" ) + hv_download_found = hashview_subparsers.add_parser( + "download-found", + help="Download found hashes for a hashfile", + ) + hv_download_found.add_argument( + "--customer-id", required=True, type=int, help="Customer ID" + ) + hv_download_found.add_argument( + "--hashfile-id", required=True, type=int, help="Hashfile ID" + ) + hv_upload_hashfile_job = hashview_subparsers.add_parser( "upload-hashfile-job", help="Upload a hashfile and create a job", @@ -2735,7 +2773,13 @@ def main(): # Removed add_common_args(parser) since config items are now only set via config file argv = sys.argv[1:] - hashview_subcommands = ["upload-cracked", "upload-wordlist", "download-left", "upload-hashfile-job"] + hashview_subcommands = [ + "upload-cracked", + "upload-wordlist", + "download-left", + "download-found", + "upload-hashfile-job", + ] has_hashview_flag = "--hashview" in argv has_hashview_subcommand = any(cmd in argv for cmd in hashview_subcommands) @@ -2850,6 +2894,15 @@ def main(): print(f" File: {download_result['output_file']}") sys.exit(0) + if args.hashview_command == "download-found": + download_result = api_harness.download_found_hashes( + args.customer_id, + args.hashfile_id, + ) + print(f"\n✓ Success: Downloaded {download_result['size']} bytes") + print(f" File: {download_result['output_file']}") + sys.exit(0) + if args.hashview_command == "upload-hashfile-job": hashfile_path = resolve_path(args.file) if not hashfile_path or not os.path.isfile(hashfile_path): diff --git a/tests/test_hashview_cli_subcommands_subprocess.py b/tests/test_hashview_cli_subcommands_subprocess.py index eb5fded..888bb6f 100644 --- a/tests/test_hashview_cli_subcommands_subprocess.py +++ b/tests/test_hashview_cli_subcommands_subprocess.py @@ -47,6 +47,10 @@ def _ensure_customer_one(): if not url or not key: pytest.skip("Missing hashview_url/hashview_api_key in config.json or env.") api = HashviewAPI(url, key) + + # Get customer ID from environment or default to 1 + customer_id = int(os.environ.get("HASHVIEW_CUSTOMER_ID", "1")) + try: customers_result = api.list_customers() except Exception as exc: @@ -56,9 +60,9 @@ def _ensure_customer_one(): if isinstance(customers_result, dict) else customers_result ) - if not any(int(cust.get("id", 0)) == 1 for cust in customers or []): - api.create_customer("Example Customer") - return 1 + if not any(int(cust.get("id", 0)) == customer_id for cust in customers or []): + api.create_customer(f"Example Customer {customer_id}") + return customer_id @pytest.mark.parametrize( @@ -226,6 +230,10 @@ def test_hashview_subcommands_live_upload_hashfile_job(tmp_path): env=env, ) output = run.stdout + run.stderr + if "Invalid customer ID" in output: + pytest.skip( + "HASHVIEW_CUSTOMER_ID does not exist for this API key. Update the env or create the customer." + ) assert run.returncode == 0, output assert ("Hashfile uploaded" in output) or ("Hashfile added" in output) assert ("Job created" in output) or ("Failed to add job" in output) @@ -312,6 +320,10 @@ def test_hashview_subcommands_live_upload_hashfile_job_pwdump(tmp_path): env=env, ) output = run.stdout + run.stderr + if "Invalid customer ID" in output: + pytest.skip( + "HASHVIEW_CUSTOMER_ID does not exist for this API key. Update the env or create the customer." + ) assert run.returncode == 0, output assert ("Hashfile uploaded" in output) or ("Hashfile added" in output) assert ("Job created" in output) or ("Failed to add job" in output)