On October 25, 2023, NVD published CVE-2023-46233 with a CVSS v3.1 score of 9.1 (critical): every version of crypto-js prior to 4.2.0 shipped a PBKDF2 key-derivation function that defaulted to SHA1 with exactly one iteration, instead of the 1,000 iterations specified by the original 1993 PKCS#5 standard. NVD's own advisory language is blunt — the default configuration is "approximately 1,300,000 times weaker than current industry standards." Nine days later, the near-identical flaw turned up in crypto-es, the ESM-native fork maintained by entronad, tracked as CVE-2023-46133 and fixed in version 2.1.0. Both libraries are still pulled into thousands of npm dependency trees — crypto-js alone sees tens of millions of weekly downloads — for AES encryption, HMAC signing, and password-based key derivation in browser and Node.js applications. The bug wasn't a coding mistake in the traditional sense; it was a default that quietly downgraded every caller who didn't explicitly override the iteration count and hash algorithm. This piece walks through the CWE classifications behind the CVSS score, why a single-iteration PBKDF2 call collapses under brute force, what the November 2023 patch actually changed, and the concrete migration steps for teams still running affected versions.
What exactly was insecure in crypto-js's default configuration?
The flaw sits in crypto-js's PBKDF2() function, which derives a key from a password and salt for use in encryption or signing. Called with no explicit options, it used SHA1 as the hash and 1 as the iteration count — configuration values meant to slow down brute-force attacks by making each guess computationally expensive. PBKDF1/PKCS#5, standardized in 1993, specified a minimum of 1,000 iterations even then; modern OWASP guidance recommends 600,000+ iterations for PBKDF2-HMAC-SHA256. A single iteration means an attacker testing password guesses against a derived key pays essentially the cost of one hash operation per guess — the same order of magnitude as hashing with no key-stretching at all. NVD's official mitigation is explicit: configure crypto-js to use SHA256 with at least 250,000 iterations rather than relying on the library's shipped defaults, since the fix in 4.2.0 only changed defaults going forward and did not retroactively re-derive existing stored keys.
Why does CVSS 9.1 apply, and what CWEs are involved?
The NVD entry assigns vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N — network-exploitable, low complexity, no privileges or user interaction required, with high confidentiality and integrity impact but no availability impact. That combination reflects how the flaw is typically triggered: no attacker action against the library itself is needed, only patience cracking already-exposed derived keys or signatures offline. Three CWEs anchor the finding: CWE-327 (Use of a Broken or Risky Cryptographic Algorithm), CWE-328 (Use of a Weak Hash), and CWE-916 (Use of Password Hash With Insufficient Computational Effort). CWE-916 is the one that matters most in practice — it's the same weakness class behind cracked password databases where the hash function itself was fine, but nobody made guessing expensive. The GitHub Security Advisory tracking this issue, GHSA-xwcq-pm8m-c4vf, and a Debian LTS security announcement in November 2023 both reference the same root defect and the same fixed commit, 421dd538b2d34e7c24a5b72cc64dc2b9167db40a, in the brix/crypto-js repository.
How does crypto-es's CVE-2023-46133 relate to it?
crypto-es is a TypeScript- and ES module-native fork of crypto-js maintained separately by developer entronad, and it inherited the exact same PBKDF2 default because the derivation logic was ported largely unchanged. GHSA-mpj8-q39x-wq5h, filed against crypto-es, describes an identical default — SHA1, one iteration — and carries the same CVSS 9.1 severity as its crypto-js counterpart. The fix landed in crypto-es 2.1.0, and the advisory recommends the same interim mitigation: explicitly pass SHA256 with 250,000+ iterations if you can't upgrade immediately. Because the two libraries share lineage but not a release cadence, teams that migrated off crypto-js toward crypto-es for its ES6/TypeScript support around the same time inherited the vulnerability under a different package name — a reminder that forking a library doesn't fork away its security debt, and SCA tooling needs to track both names independently rather than assuming a fork inherits a fix.
Why did this go unnoticed for so long?
crypto-js first published its PBKDF2 implementation years before either CVE was assigned, and the weak default survived multiple major versions because PBKDF2 "worked" in the sense that it produced a key — it just didn't do the computational work that makes brute-forcing expensive. Unlike a crash or an obviously wrong output, a fast KDF looks identical in unit tests and in production traffic; the only way to notice is to explicitly audit the iteration count against a standard, which most consuming applications never did because they trusted the library's defaults. This is a recurring pattern in the JavaScript crypto ecosystem: crypto-js predates the Web Crypto API's standardization and became a de facto default for browser-side crypto because native alternatives were inconsistent across older browsers, so its defaults were copied into countless tutorials and boilerplate projects without scrutiny. The fix in 4.2.0 changed the library's shipped default going forward, but every key, encrypted blob, or password hash generated before the upgrade — using the old default — remains only as strong as a single SHA1 pass, regardless of when the dependency itself gets bumped.
What's the safe migration path?
The immediate fix is trivial to write but easy to get wrong silently: upgrade to crypto-js 4.2.0+ or crypto-es 2.1.0+, and — critically — audit every call site that invokes PBKDF2() to confirm it explicitly passes { keySize, iterations: 250000, hasher: CryptoJS.algo.SHA256 } or higher, since upgrading the package alone does not change behavior for code that already overrides the hash or iteration count with weak values. Any data protected under the old default — password verifiers, derived encryption keys, HMAC secrets — should be treated as compromised and re-derived from the original password or re-encrypted under a freshly generated key, not just re-hashed with the new defaults. For new projects, consider migrating off PBKDF2 entirely toward Argon2id or scrypt, both purpose-built to resist GPU and ASIC brute-forcing in ways PBKDF2 fundamentally cannot, and both available via well-maintained libraries like argon2 or the Web Crypto API's SubtleCrypto for browser contexts.
How Safeguard helps
crypto-js and crypto-es rarely appear as direct dependencies in a modern package.json — they're far more often buried three or four levels deep, pulled in by an unrelated SDK or a legacy auth helper nobody remembers adding. Safeguard's deep dependency scanning resolves npm trees to 100 levels deep, so a vulnerable crypto-js sitting under an SDK's SDK still surfaces with its full transitive path back to your root manifest, instead of getting lost in a shallow SCA scan. Once a match against CVE-2023-46233 or CVE-2023-46133 is found, Griffin AI cross-references EPSS and CISA KEV data and runs reachability analysis to confirm your code actually calls PBKDF2() before ranking it — separating projects using crypto-js only for AES encryption (unaffected) from those relying on its password-derivation path (critical). From there, AI Remediate can generate a pull request bumping the dependency and flagging call sites that still need an explicit iteration-count override, turning a CVSS 9.1 finding into a reviewed fix rather than a ticket that sits untriaged.