Passbolt: a bold use of HaveIBeenPwned

Passbolt’s browser extension queried the Pwned Passwords API while users typed, emitting successive 5-nibble SHA1 prefixes that an observer can collect and combine to recover long passwords; the authors implemented a Hashcat module and GPU kernels to demonstrate practical recovery within seconds to days depending on hardware. The issue was fixed in Passbolt extension v4.6.2 by only querying on save and adding an entropy threshold. #Passbolt #PwnedPasswords

Keypoints

  • Passbolt integrated HaveIBeenPwned’s Pwned Passwords API and queried it interactively while users typed new passwords, sending the first 5 nibbles (20 bits) of SHA1(password) per query.
  • Because queries were emitted as the user typed (debounced ~300 ms), an observer who captures those API requests can collect multiple partial SHA1 prefixes for successive password lengths and correlate them to reconstruct the full password.
  • The researchers built a Hashcat module (module_90100) and custom OpenCL kernels that compare candidates against four intercepted 5-nibble prefixes, recovering 8→11+ character passwords; optimized GPU runs achieved multi-GH/s rates enabling practical recovery.
  • A PoC showed first 8 characters recovered in seconds on a laptop GPU; full 11+ character recovery depends on charset and hardware (worst-case ~15 days on one GPU, hours on many high-end GPUs or much faster for human-chosen subsets).
  • Variants tolerate missing intermediate prefixes or insert/edit typing patterns; attackers need only the ability to observe Passbolt plugin API calls (e.g., TLS proxy, network observer, cached nodes).
  • Passbolt remediated by querying only when saving and only for passwords above ~60 bits of estimated entropy; Pwned Passwords APIv3 docs were updated with a warning about incremental searches.

MITRE Techniques

  • [T1056] Input Capture – Passbolt “emits queries as soon as you type” enabling capture of incremental password-derived data (‘The first naive thought … if it emits queries as soon as you type, you could easily guess which first char was typed in.’).
  • [T1040] Network Sniffing – An attacker observing Passbolt queries to the Pwned Passwords API can collect partial SHA1 prefixes (‘The only technical requirement is to be able to observe Passbolt plugin queries to the Pwned Password API.’).
  • [T1071.001] Application Layer Protocol: Web Protocols – The attack relies on repeated HTTPS GET requests to api.pwnedpasswords.com/range/xxxxx sending 5-nibble SHA1 prefixes (‘send the first 5 nibbles … to the service API’).
  • [T1110] Brute Force – The PoC uses GPU-accelerated brute force (Hashcat OpenCL kernels) to generate candidates and filter them against intercepted partial hashes (‘Let’s build a Hashcat module that will … crack the 11-char password’ and reported MH/s performance figures).

Indicators of Compromise

  • [Domain] Observed API endpoint – api.pwnedpasswords.com (e.g., https://api.pwnedpasswords.com/range/36e61)
  • [SHA1 full hash] Example full SHA1 seen in examples – 36e618512a68721f032470bb0891adef3362cfa9, d452118ad7a65b151ac323e8abab71737896fc46
  • [Partial SHA1 prefixes] 5-nibble prefixes used in queries – 36e61, d4521, bb0c6, 814f8 (and other prefixes listed in examples)
  • [File/module names] Hashcat module and kernels mentioned – src/modules/module_90100.c, OpenCL/m90100_a3-pure.cl (and OpenCL/m90100_a3-optimized.cl)
  • [Commands / artifacts] Example PoC artifacts – passbolt_generate_partial_hashes.py, passbolt_recovery_step2.py (used to generate and recover from partial hashes)

Passbolt’s interactive use of the Pwned Passwords range API leaks successive 5-nibble SHA1 prefixes as a user types; capturing those timed requests yields multiple partial hashes corresponding to successive prefix lengths. The authors instrumented the Passbolt extension, observed the 300 ms debounce and the API calls, then designed a Hashcat module (module_90100) and OpenCL kernels that treat the intercepted 5-nibble segments as targets: the module encodes four 5-nibble values into a 32-byte-like “hash” structure and the kernel logic filters candidate passwords by shifting and comparing the first 20 bits of SHA1 for each intermediate length, extending surviving candidates byte-by-byte to match subsequent prefixes. Benchmarks show the pure and optimized kernels reached several thousand MH/s on an RTX-2000 laptop (3.4–4.0 GH/s), enabling practical recovery of the first 8 characters in seconds for constrained charsets and full 11+ char recovery within feasible time on high-end GPU fleets; the authors also provide Python scripts to generate partial prefixes and perform the final per-character extension once the 8-char base is found.

Implementation details emphasized: reuse the SHA1 module as base, change DGST_POS constants to align 5-nibble segments, adapt kernel comparison logic to shift ctx.h[] >> 12 and compare to search[] entries, and remove incompatible SHA1 precomputations (e_rev, SHA1M_A adjustments) for optimized kernels. The PoC includes a brute-force a3 kernel variant that, upon matching the first partial prefix, nests loops to guess subsequent characters (p9, p10, p11) and recomputes SHA1 in-kernel to validate each additional prefix before marking a found password; for practical attacks, variants can tolerate missed intermediate prefixes or edits during typing but still require multiple partial hashes to reduce candidate space to tractable sizes.

The demonstrated mitigation is to avoid incremental checks while typing: Passbolt updated its extension to query Pwned Passwords only on save and only for passwords above ~60 bits of entropy, and the Pwned Passwords API documentation was updated to warn about incremental searches; defenders should monitor for atypical api.pwnedpasswords.com/range/xxxxx requests from browser extensions or TLS proxies and consider blocking or inspecting such patterns in environments where API calls could be observed. Read more: https://blog.quarkslab.com/passbolt-a-bold-use-of-haveibeenpwned.html