mirror of
https://github.com/trustedsec/hate_crack.git
synced 2026-07-28 14:47:22 -07:00
feat(hashview): display a customer's hashfiles instead of asking for type
Downloading previously required knowing/typing the hash type because Hashview only lists hashfiles per type. The download flow now sweeps common hashcat modes (HashviewAPI.get_all_customer_hashfiles) and shows all of the customer's uploaded hashfiles to pick from, deduped by id. A manual hash-type prompt remains as a fallback only when nothing is found among the common set (uncommon modes). The chosen file's real hash type still drives the download. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
ab4700e636
commit
347e3529d5
@@ -913,7 +913,7 @@ Interactive menu for downloading and managing wordlists from Weakpass.com via Bi
|
||||
### Version History
|
||||
|
||||
Version 2.10.6
|
||||
- Fixed the Hashview integration calling API routes that don't exist in Hashview (verified against v0.8.3-dev), which 404'd as soon as a customer ID was entered ("Could not list hashfiles"). The customer→hashfile listing relied on a phantom `GET /v1/hashfiles` list-all route; it now enumerates via the real `GET /v1/hashfiles/hash_type/<type>` endpoint, scoped to the session hash type (prompting for a hash type when none is loaded this session), and filters by customer. Additional client-side route fixes: hashfile hash-type lookup now uses `GET /v1/getHashType/<id>`; "left" (uncracked) hash download uses `GET /v1/hashfiles/<id>`; `delete_job` uses `DELETE /v1/jobs/<id>`; `start_job` uses `POST`. Hashview exposes no stop-job route, so `stop_job` now raises with guidance to use `delete_job`; and no bulk cracked-hash export exists, so the best-effort "found" merge degrades gracefully
|
||||
- Fixed the Hashview integration calling API routes that don't exist in Hashview (verified against v0.8.3-dev), which 404'd as soon as a customer ID was entered ("Could not list hashfiles"). The customer→hashfile listing relied on a phantom `GET /v1/hashfiles` list-all route; it now enumerates via the real `GET /v1/hashfiles/hash_type/<type>` endpoint. Since that route lists per hash type, the download flow sweeps common hashcat modes to display all of a customer's uploaded hashfiles (falling back to a manual hash-type prompt only if none are found among the common set), then filters by customer. Additional client-side route fixes: hashfile hash-type lookup now uses `GET /v1/getHashType/<id>`; "left" (uncracked) hash download uses `GET /v1/hashfiles/<id>`; `delete_job` uses `DELETE /v1/jobs/<id>`; `start_job` uses `POST`. Hashview exposes no stop-job route, so `stop_job` now raises with guidance to use `delete_job`; and no bulk cracked-hash export exists, so the best-effort "found" merge degrades gracefully
|
||||
|
||||
Version 2.10.5
|
||||
- Pipal analysis no longer corrupts its input when cracked passwords contain `$HEX[...]` rows. `binascii.unhexlify().decode()` returned the bytes without the trailing newline that normal rows inherit from `password[-1]`, so every HEX-encoded password got concatenated with the next one in the `.passwords` file fed to pipal (e.g. three cracks → two lines, one of them a bogus mashup). Pipal then under-counted entries and reported wrong top base words. The HEX branch now re-appends `\n` so each cracked password lands on its own line
|
||||
|
||||
@@ -1211,6 +1211,75 @@ class HashviewAPI:
|
||||
|
||||
return customer_hfs
|
||||
|
||||
# Curated set of hashcat modes commonly seen in engagements. Used to
|
||||
# enumerate a customer's hashfiles by sweeping the per-type listing
|
||||
# endpoint, since Hashview exposes no list-all route. Not exhaustive:
|
||||
# uncommon types can still be queried explicitly via get_customer_hashfiles.
|
||||
COMMON_HASH_TYPES = (
|
||||
0, # MD5
|
||||
100, # SHA1
|
||||
1000, # NTLM
|
||||
3000, # LM
|
||||
1100, # Domain Cached Credentials (DCC), MS Cache
|
||||
2100, # Domain Cached Credentials 2 (DCC2), MS Cache 2
|
||||
5500, # NetNTLMv1
|
||||
5600, # NetNTLMv2
|
||||
7500, # Kerberos 5 AS-REQ Pre-Auth (etype 23)
|
||||
13100, # Kerberos 5 TGS-REP (Kerberoasting, etype 23)
|
||||
18200, # Kerberos 5 AS-REP (AS-REP roasting, etype 23)
|
||||
19600, # Kerberos 5 TGS-REP (etype 17)
|
||||
19700, # Kerberos 5 TGS-REP (etype 18)
|
||||
1800, # sha512crypt $6$ (Linux)
|
||||
500, # md5crypt $1$ (Linux/Cisco)
|
||||
7400, # sha256crypt $5$
|
||||
3200, # bcrypt $2*$
|
||||
1700, # SHA512
|
||||
1400, # SHA256
|
||||
160, # HMAC-SHA1
|
||||
13400, # KeePass
|
||||
9600, # MS Office 2013
|
||||
10500, # PDF 1.4-1.6
|
||||
11600, # 7-Zip
|
||||
16500, # JWT
|
||||
22000, # WPA-PBKDF2-PMKID+EAPOL
|
||||
)
|
||||
|
||||
def get_all_customer_hashfiles(self, customer_id, hash_types=None):
|
||||
"""Aggregate a customer's hashfiles across hash types (deduped by id).
|
||||
|
||||
Hashview has no list-all route, so this sweeps the per-type listing
|
||||
endpoint (:meth:`get_hashfiles_by_type`) over ``hash_types`` and keeps
|
||||
the files belonging to ``customer_id``. Defaults to
|
||||
:attr:`COMMON_HASH_TYPES`; a file of an uncommon type not in the sweep
|
||||
won't appear. The first hash type a file is seen under is retained as
|
||||
its ``hash_type`` (mixed-type files are rare).
|
||||
"""
|
||||
if hash_types is None:
|
||||
hash_types = self.COMMON_HASH_TYPES
|
||||
seen = {}
|
||||
for ht in hash_types:
|
||||
try:
|
||||
files = self.get_hashfiles_by_type(ht)
|
||||
except Exception as e:
|
||||
if self.debug:
|
||||
print(f"[DEBUG] get_all_customer_hashfiles: type {ht} failed: {e}")
|
||||
continue
|
||||
for hf in files:
|
||||
if int(hf.get("customer_id", 0)) != int(customer_id):
|
||||
continue
|
||||
hf_id = hf.get("id")
|
||||
if hf_id is None:
|
||||
continue
|
||||
if not (hf.get("hashtype") or hf.get("hash_type")):
|
||||
hf["hash_type"] = str(ht)
|
||||
seen.setdefault(int(hf_id), hf)
|
||||
if self.debug:
|
||||
print(
|
||||
f"[DEBUG] get_all_customer_hashfiles({customer_id}): "
|
||||
f"found {len(seen)} hashfiles across {len(hash_types)} types"
|
||||
)
|
||||
return list(seen.values())
|
||||
|
||||
def get_customer_hashfiles_with_hashtype(self, customer_id, target_hashtype="1000"):
|
||||
"""Return hashfiles for a customer that match the requested hashtype."""
|
||||
customer_hashfiles = self.get_customer_hashfiles(
|
||||
|
||||
+34
-23
@@ -3759,34 +3759,45 @@ def hashview_api():
|
||||
continue
|
||||
|
||||
# List hashfiles for the customer. Hashview has no
|
||||
# list-all route, so we must enumerate by hash type.
|
||||
# Prefer the session hash type (set when a hashfile is
|
||||
# loaded); otherwise prompt, since None can't be queried.
|
||||
download_hash_type = hcatHashType
|
||||
if not download_hash_type:
|
||||
ht_input = input(
|
||||
"\nNo hash type loaded this session. Enter the hashcat "
|
||||
"hash-type to list (e.g. 1000 for NTLM), or Q to cancel: "
|
||||
).strip()
|
||||
if ht_input.lower() == "q":
|
||||
cancel_download = True
|
||||
break
|
||||
if not ht_input.isdigit():
|
||||
print(
|
||||
"\n✗ Error: Hash type must be a numeric hashcat mode."
|
||||
)
|
||||
continue
|
||||
download_hash_type = ht_input
|
||||
|
||||
# list-all route, so we enumerate by sweeping the
|
||||
# per-type endpoint across common hashcat modes and
|
||||
# showing whatever the customer has uploaded.
|
||||
try:
|
||||
customer_hashfiles = api_harness.get_customer_hashfiles(
|
||||
customer_id, hash_type=download_hash_type
|
||||
print(
|
||||
"\nScanning customer hashfiles across common hash types..."
|
||||
)
|
||||
customer_hashfiles = api_harness.get_all_customer_hashfiles(
|
||||
customer_id
|
||||
)
|
||||
|
||||
# Fallback: nothing among common types. The file may
|
||||
# use an uncommon mode, so let the user name it.
|
||||
if not customer_hashfiles:
|
||||
print(
|
||||
f"\nNo hashfiles found for customer ID {customer_id} "
|
||||
"among common hash types."
|
||||
)
|
||||
ht_input = input(
|
||||
"Enter a specific hashcat hash-type to check "
|
||||
"(e.g. 1000), or Q to cancel: "
|
||||
).strip()
|
||||
if ht_input.lower() == "q":
|
||||
cancel_download = True
|
||||
break
|
||||
if not ht_input.isdigit():
|
||||
print(
|
||||
"\n✗ Error: Hash type must be a numeric hashcat mode."
|
||||
)
|
||||
continue
|
||||
customer_hashfiles = (
|
||||
api_harness.get_customer_hashfiles(
|
||||
customer_id, hash_type=ht_input
|
||||
)
|
||||
)
|
||||
|
||||
if not customer_hashfiles:
|
||||
print(
|
||||
f"\nNo hashfiles of type {download_hash_type} found "
|
||||
f"for customer ID {customer_id}"
|
||||
f"\nNo hashfiles found for customer ID {customer_id}"
|
||||
)
|
||||
continue
|
||||
|
||||
|
||||
@@ -95,6 +95,41 @@ class TestHashviewAPI:
|
||||
result = api.get_customer_hashfiles(1)
|
||||
assert result == []
|
||||
|
||||
def test_get_all_customer_hashfiles_sweeps_and_dedupes(self, api):
|
||||
"""Aggregate sweeps per-type listings, filters by customer, dedupes by id."""
|
||||
per_type = {
|
||||
1000: [
|
||||
{"id": 1, "customer_id": 1, "name": "ntlm.txt", "hash_type": 1000},
|
||||
{"id": 2, "customer_id": 2, "name": "other.txt", "hash_type": 1000},
|
||||
],
|
||||
5600: [
|
||||
{"id": 3, "customer_id": 1, "name": "ntlmv2.txt", "hash_type": 5600},
|
||||
# id 1 appears again under another type; must dedupe (first wins)
|
||||
{"id": 1, "customer_id": 1, "name": "ntlm.txt", "hash_type": 5600},
|
||||
],
|
||||
}
|
||||
api.get_hashfiles_by_type = Mock(side_effect=lambda ht: per_type.get(int(ht), []))
|
||||
|
||||
result = api.get_all_customer_hashfiles(1, hash_types=[1000, 5600])
|
||||
|
||||
ids = sorted(hf["id"] for hf in result)
|
||||
assert ids == [1, 3] # customer 2 excluded, id 1 not duplicated
|
||||
by_id = {hf["id"]: hf for hf in result}
|
||||
assert str(by_id[1]["hash_type"]) == "1000" # first type seen wins
|
||||
assert str(by_id[3]["hash_type"]) == "5600"
|
||||
|
||||
def test_get_all_customer_hashfiles_skips_failing_types(self, api):
|
||||
"""A per-type query that errors is skipped, not fatal."""
|
||||
|
||||
def _by_type(ht):
|
||||
if int(ht) == 1000:
|
||||
raise RuntimeError("boom")
|
||||
return [{"id": 9, "customer_id": 1, "name": "x", "hash_type": int(ht)}]
|
||||
|
||||
api.get_hashfiles_by_type = Mock(side_effect=_by_type)
|
||||
result = api.get_all_customer_hashfiles(1, hash_types=[1000, 5600])
|
||||
assert [hf["id"] for hf in result] == [9]
|
||||
|
||||
def test_get_customer_hashfiles(self, api):
|
||||
"""Filter the type-scoped hashfile list by customer_id (real API if possible)."""
|
||||
hashview_url, hashview_api_key = self._get_hashview_config()
|
||||
|
||||
Reference in New Issue
Block a user