ghsa-h7cp-r72f-jxh6
Vulnerability from github
Summary
This affects both:
1. Unsupported algos (e.g. sha3-256
/ sha3-512
/ sha512-256
)
2. Supported but non-normalized algos (e.g. Sha256
/ Sha512
/ SHA1
/ sha-1
/ sha-256
/ sha-512
)
All of those work correctly in Node.js, but this polyfill silently returns highly predictable ouput
Under Node.js (only with pbkdf2/browser
import, unlikely) / Bun (pbkdf2
top-level import is affected), the memory is not zero-filled but is uninitialized, as Buffer.allocUnsafe
is used
Under browsers, it just returns zero-filled buffers (Which is also critical, those are completely unacceptable as kdf output and ruin security)
Were you affected?
The full list of arguments that were not affected were literal:
* 'md5'
* 'sha1'
* 'sha224'
* 'sha256'
* 'sha384'
* 'sha512'
* 'rmd160'
* 'ripemd160'
Any other arguments, e.g. representation variations of the above ones like 'SHA-1'
/'sha-256'
/'SHA512'
or different algos like 'sha3-512'
/'blake2b512'
, while supported on Node.js crypto
module, returned predictable output on pbkdf2
(or crypto
browser/bundlers polyfill)
Beware of packages re-exporting this under a different signature, like (abstract):
js
const crypto = require('crypto')
module.exports.deriveKey = (algo, pass, salt) => crypto.pbkdf2Sync(pass, salt, 2048, 64, algo)
In this case, the resulting deriveKey
method is also affected (to the same extent / conditions as listed here).
Environments
This affects require('crypto')
in polyfilled mode (e.g. from crypto-browserify
, node-libs-browser
, vite-plugin-node-polyfills
, node-stdlib-browser
, etc. -- basically everything that bundles/polfyills crypto
- In bundled code (e.g. Webpack / Vite / whatever), this affects
require('crypto')
andrequire('pbkdf2')
- On Node.js, this does not affect
require('pbkdf2')
(orrequire('crypto')
obviously), but affectsrequire('pbkdf2/browser')
- On Bun, this does affect
require('pbkdf2')
andrequire('pbkdf2/browser')
(and returns uninitialized memory, often zeros / sparse flipped bytes)
PoC
```js const node = require('crypto') const polyfill = require('pbkdf2/browser')
const algos = [
'sha3-512', 'sha3-256', 'SHA3-384',
'Sha256', 'Sha512', 'sha512-256',
'SHA1', 'sha-1',
'blake2b512',
'RMD160', 'RIPEMD-160', 'ripemd-160',
]
for (const algo of algos) {
for (const { pbkdf2Sync } of [node, polyfill]) {
const key = pbkdf2Sync('secret', 'salt', 100000, 64, algo)
console.log(${algo}: ${key.toString('hex')}
);
}
}
```
Output (odd lines are Node.js, even is pbkdf2
module / polyfill):
sha3-512: de00370414a3251d6d620dc8f7c371644e5d7f365ab23b116298a23fa4077b39deab802dd61714847a5c7e9981704ffe009aee5bb40f6f0103fc60f3d4cedfb0
sha3-512: 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
sha3-256: 76bf06909b91e4c968700078ee36af92019d0839ab1fea2f345c6c8685074ca0179302633fbd84d22cff4f8744952b2d07edbfc9658e95d30fb4e93ee067c7c9
sha3-256: 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
SHA3-384: 2b2b41b73f9b7bcd023f709ea84ba3c29a88edc311b737856ba9e74a2d9a928f233eb8cb404a9ba93c276edf6380c692140024a0bc12b75bfa38626207915e01
SHA3-384: 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
Sha256: 3fa094211c0cf2ed1d332ab43adc69aab469f0e0f2cae6345c81bb874eef3f9eb2c629052ec272ca49c2ee95b33e7ba6377b2317cd0dacce92c4748d3c7a45f0
Sha256: 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
Sha512: 3745e482c6e0ade35da10139e797157f4a5da669dad7d5da88ef87e47471cc47ed941c7ad618e827304f083f8707f12b7cfdd5f489b782f10cc269e3c08d59ae
Sha512: 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
sha512-256: e423f61987413121418715d0ebf64cb646042ae9a09fe4fd2c764a4f186ba28cf70823fdc2b03dda67a0d977c6f0a0612e5ed74a11e6f32b033cb658fa9f270d
sha512-256: 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
SHA1: 0e24bc5a548b236e3eb3b22317ef805664a88747c725cd35bfb0db0e4ae5539e3ed5cd5ba8c0ac018deb6518059788c8fffbe624f614fbbe62ba6a6e174e4a72
SHA1: 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
sha-1: 0e24bc5a548b236e3eb3b22317ef805664a88747c725cd35bfb0db0e4ae5539e3ed5cd5ba8c0ac018deb6518059788c8fffbe624f614fbbe62ba6a6e174e4a72
sha-1: 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
blake2b512: d3d661100c5ffb79bdf3b5c77d1698e621414cba40e2348bd3f1b10fbd2fe97bff4dc7d76474955bfefa61179f2a37e9dddedced0e7e79ef9d8c678080d45926
blake2b512: 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
RMD160: ec65dbad1485616cf0426725d64e009ad3e1633543746ccb56b7f06eb7ce51d0249aaef27c879f32911a7c0accdc83389c2948ddec439114f6165366f9b4cca2
RMD160: 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
RIPEMD-160: ec65dbad1485616cf0426725d64e009ad3e1633543746ccb56b7f06eb7ce51d0249aaef27c879f32911a7c0accdc83389c2948ddec439114f6165366f9b4cca2
RIPEMD-160: 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
ripemd-160: ec65dbad1485616cf0426725d64e009ad3e1633543746ccb56b7f06eb7ce51d0249aaef27c879f32911a7c0accdc83389c2948ddec439114f6165366f9b4cca2
ripemd-160: 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
Uninitialized memory
```js const { pbkdf2Sync } = require('pbkdf2/browser') // or just 'pbkdf2' on Bun will do this too
let prev for (let i = 0; i < 100000; i++) { const key = pbkdf2Sync('secret', 'salt', 100000, 64, 'sha3-256') const hex = key.toString('hex') if (hex !== prev) console.log(hex); prev = hex } ```
Affected versions
Seems to be since https://github.com/browserify/pbkdf2/commit/9699045c37a07f8319cfb8d44e2ff4252d7a7078
Impact
This is critical, browserifying code might silently generate zero-filled keys instead of proper ones, for code that was working on Node.js or in test environment
Just updating to a fixed version is not enough: if anyone was using pbkdf2
lib (e.g. via crypto-browserify
or directly) on algos not from the literal string list (see "were you affected"), recheck where those keys went / how they were used, and take action accordingly
Note
Most likely, you receive this either through a subdep using pbkdf2
module directly (and then it is used), or through crypto-browserify
(and the usage depends on whether you or any of your subdeps were calling pbkdf2/pbkdf2Sync
methods from Node.js crypto inside your bundle)
When targeting non-Node.js, prever avoiding Node.js crypto polyfill at all, and use crypto.subtle
and/or modern/audited cryptography primitives instead
{ "affected": [ { "database_specific": { "last_known_affected_version_range": "\u003c= 3.1.2" }, "package": { "ecosystem": "npm", "name": "pbkdf2" }, "ranges": [ { "events": [ { "introduced": "3.0.10" }, { "fixed": "3.1.3" } ], "type": "ECOSYSTEM" } ] } ], "aliases": [ "CVE-2025-6545" ], "database_specific": { "cwe_ids": [ "CWE-20" ], "github_reviewed": true, "github_reviewed_at": "2025-06-23T22:41:50Z", "nvd_published_at": "2025-06-23T19:15:25Z", "severity": "CRITICAL" }, "details": "### Summary\n\nThis affects both:\n 1. Unsupported algos (e.g. `sha3-256` / `sha3-512` / `sha512-256`)\n 2. Supported but non-normalized algos (e.g. `Sha256` / `Sha512` / `SHA1` / `sha-1` / `sha-256` / `sha-512`)\n\nAll of those work correctly in Node.js, but this polyfill silently returns highly predictable ouput\n\nUnder Node.js (only with `pbkdf2/browser` import, unlikely) / Bun (`pbkdf2` top-level import is affected), the memory is not zero-filled but is uninitialized, as `Buffer.allocUnsafe` is used\n\nUnder browsers, it just returns zero-filled buffers\n(Which is also critical, those are completely unacceptable as kdf output and ruin security)\n\n### Were you affected?\n\nThe full list of arguments that were **not** affected were literal:\n * `\u0027md5\u0027`\n * `\u0027sha1\u0027`\n * `\u0027sha224\u0027`\n * `\u0027sha256\u0027`\n * `\u0027sha384\u0027`\n * `\u0027sha512\u0027`\n * `\u0027rmd160\u0027`\n * `\u0027ripemd160\u0027`\n\nAny other arguments, e.g. representation variations of the above ones like `\u0027SHA-1\u0027`/`\u0027sha-256\u0027`/`\u0027SHA512\u0027` or different algos like `\u0027sha3-512\u0027`/`\u0027blake2b512\u0027`, while supported on Node.js `crypto` module, returned predictable output on `pbkdf2` (or `crypto` browser/bundlers polyfill)\n\n---\n\nBeware of packages re-exporting this under a different signature, like (abstract):\n```js\nconst crypto = require(\u0027crypto\u0027)\nmodule.exports.deriveKey = (algo, pass, salt) =\u003e crypto.pbkdf2Sync(pass, salt, 2048, 64, algo)\n```\n\nIn this case, the resulting `deriveKey` method is also affected (to the same extent / conditions as listed here).\n\n### Environments\n\nThis affects `require(\u0027crypto\u0027)` in polyfilled mode (e.g. from `crypto-browserify`, `node-libs-browser`, `vite-plugin-node-polyfills`, `node-stdlib-browser`, etc. -- basically everything that bundles/polfyills `crypto`\n\n* In bundled code (e.g. Webpack / Vite / whatever), this affects `require(\u0027crypto\u0027)` and `require(\u0027pbkdf2\u0027)`\n* On Node.js, this does not affect `require(\u0027pbkdf2\u0027)` (or `require(\u0027crypto\u0027)` obviously), but affects `require(\u0027pbkdf2/browser\u0027)`\n* On Bun, this _does_ affect `require(\u0027pbkdf2\u0027)` _and_ `require(\u0027pbkdf2/browser\u0027)` (and returns uninitialized memory, often zeros / sparse flipped bytes)\n\n### PoC\n```js\nconst node = require(\u0027crypto\u0027)\nconst polyfill = require(\u0027pbkdf2/browser\u0027)\n\nconst algos = [\n \u0027sha3-512\u0027, \u0027sha3-256\u0027, \u0027SHA3-384\u0027,\n \u0027Sha256\u0027, \u0027Sha512\u0027, \u0027sha512-256\u0027,\n \u0027SHA1\u0027, \u0027sha-1\u0027,\n \u0027blake2b512\u0027,\n \u0027RMD160\u0027, \u0027RIPEMD-160\u0027, \u0027ripemd-160\u0027,\n]\nfor (const algo of algos) {\n for (const { pbkdf2Sync } of [node, polyfill]) {\n const key = pbkdf2Sync(\u0027secret\u0027, \u0027salt\u0027, 100000, 64, algo)\n console.log(`${algo}: ${key.toString(\u0027hex\u0027)}`);\n }\n}\n```\n\nOutput (odd lines are Node.js, even is `pbkdf2` module / polyfill):\n```\nsha3-512: de00370414a3251d6d620dc8f7c371644e5d7f365ab23b116298a23fa4077b39deab802dd61714847a5c7e9981704ffe009aee5bb40f6f0103fc60f3d4cedfb0\nsha3-512: 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\nsha3-256: 76bf06909b91e4c968700078ee36af92019d0839ab1fea2f345c6c8685074ca0179302633fbd84d22cff4f8744952b2d07edbfc9658e95d30fb4e93ee067c7c9\nsha3-256: 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\nSHA3-384: 2b2b41b73f9b7bcd023f709ea84ba3c29a88edc311b737856ba9e74a2d9a928f233eb8cb404a9ba93c276edf6380c692140024a0bc12b75bfa38626207915e01\nSHA3-384: 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\nSha256: 3fa094211c0cf2ed1d332ab43adc69aab469f0e0f2cae6345c81bb874eef3f9eb2c629052ec272ca49c2ee95b33e7ba6377b2317cd0dacce92c4748d3c7a45f0\nSha256: 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\nSha512: 3745e482c6e0ade35da10139e797157f4a5da669dad7d5da88ef87e47471cc47ed941c7ad618e827304f083f8707f12b7cfdd5f489b782f10cc269e3c08d59ae\nSha512: 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\nsha512-256: e423f61987413121418715d0ebf64cb646042ae9a09fe4fd2c764a4f186ba28cf70823fdc2b03dda67a0d977c6f0a0612e5ed74a11e6f32b033cb658fa9f270d\nsha512-256: 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\nSHA1: 0e24bc5a548b236e3eb3b22317ef805664a88747c725cd35bfb0db0e4ae5539e3ed5cd5ba8c0ac018deb6518059788c8fffbe624f614fbbe62ba6a6e174e4a72\nSHA1: 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\nsha-1: 0e24bc5a548b236e3eb3b22317ef805664a88747c725cd35bfb0db0e4ae5539e3ed5cd5ba8c0ac018deb6518059788c8fffbe624f614fbbe62ba6a6e174e4a72\nsha-1: 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\nblake2b512: d3d661100c5ffb79bdf3b5c77d1698e621414cba40e2348bd3f1b10fbd2fe97bff4dc7d76474955bfefa61179f2a37e9dddedced0e7e79ef9d8c678080d45926\nblake2b512: 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\nRMD160: ec65dbad1485616cf0426725d64e009ad3e1633543746ccb56b7f06eb7ce51d0249aaef27c879f32911a7c0accdc83389c2948ddec439114f6165366f9b4cca2\nRMD160: 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\nRIPEMD-160: ec65dbad1485616cf0426725d64e009ad3e1633543746ccb56b7f06eb7ce51d0249aaef27c879f32911a7c0accdc83389c2948ddec439114f6165366f9b4cca2\nRIPEMD-160: 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\nripemd-160: ec65dbad1485616cf0426725d64e009ad3e1633543746ccb56b7f06eb7ce51d0249aaef27c879f32911a7c0accdc83389c2948ddec439114f6165366f9b4cca2\nripemd-160: 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\n```\n\n#### Uninitialized memory\n\n```js\nconst { pbkdf2Sync } = require(\u0027pbkdf2/browser\u0027) // or just \u0027pbkdf2\u0027 on Bun will do this too\n\nlet prev\nfor (let i = 0; i \u003c 100000; i++) {\n const key = pbkdf2Sync(\u0027secret\u0027, \u0027salt\u0027, 100000, 64, \u0027sha3-256\u0027)\n const hex = key.toString(\u0027hex\u0027)\n if (hex !== prev) console.log(hex);\n prev = hex\n}\n```\n\n### Affected versions\n\nSeems to be since https://github.com/browserify/pbkdf2/commit/9699045c37a07f8319cfb8d44e2ff4252d7a7078\n\n### Impact\n\nThis is critical, browserifying code might silently generate zero-filled keys instead of proper ones, for code that was working on Node.js or in test environment\n\nJust updating to a fixed version is not enough: if anyone was using `pbkdf2` lib (e.g. via `crypto-browserify` or directly) on algos not from the literal string list (see \"were you affected\"), recheck where those keys went / how they were used, and take action accordingly\n\n### Note\n\nMost likely, you receive this either through a subdep using `pbkdf2` module directly (and then it is used), or through `crypto-browserify` (and the usage depends on whether you or any of your subdeps were calling `pbkdf2/pbkdf2Sync` methods from Node.js crypto inside your bundle)\n\nWhen targeting non-Node.js, prever avoiding Node.js crypto polyfill at all, and use `crypto.subtle` and/or modern/audited cryptography primitives instead", "id": "GHSA-h7cp-r72f-jxh6", "modified": "2025-06-27T23:38:36Z", "published": "2025-06-23T22:41:50Z", "references": [ { "type": "WEB", "url": "https://github.com/browserify/pbkdf2/security/advisories/GHSA-h7cp-r72f-jxh6" }, { "type": "ADVISORY", "url": "https://nvd.nist.gov/vuln/detail/CVE-2025-6545" }, { "type": "WEB", "url": "https://github.com/browserify/pbkdf2/commit/9699045c37a07f8319cfb8d44e2ff4252d7a7078" }, { "type": "WEB", "url": "https://github.com/browserify/pbkdf2/commit/e3102a8cd4830a3ac85cd0dd011cc002fdde33bb" }, { "type": "PACKAGE", "url": "https://github.com/browserify/pbkdf2" } ], "schema_version": "1.4.0", "severity": [ { "score": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:L/VI:H/VA:N/SC:H/SI:H/SA:H", "type": "CVSS_V4" } ], "summary": "pbkdf2 returns predictable uninitialized/zero-filled memory for non-normalized or unimplemented algos" }
Sightings
Author | Source | Type | Date |
---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or seen somewhere by the user.
- Confirmed: The vulnerability is confirmed from an analyst perspective.
- Exploited: This vulnerability was exploited and seen by the user reporting the sighting.
- Patched: This vulnerability was successfully patched by the user reporting the sighting.
- Not exploited: This vulnerability was not exploited or seen by the user reporting the sighting.
- Not confirmed: The user expresses doubt about the veracity of the vulnerability.
- Not patched: This vulnerability was not successfully patched by the user reporting the sighting.