| Gói / sản phẩm | Phiên bản bị ảnh hưởng | Phiên bản đã vá |
|---|---|---|
| browserslist | < 4.28.7 | 4.28.7 |
File: node.js
Function: normalizeStats() (line ~214), reached from getStat() (called
unconditionally on every browserslist() call) and loadStat()
function normalizeStats(data, stats) {
if (!data) { data = {} }
if (stats && 'dataByBrowser' in stats) { stats = stats.dataByBrowser }
if (typeof stats !== 'object') return undefined
var normalized = {}
for (var i in stats) {
var versions = Object.keys(stats[i])
if (versions.length === 1 && data[i] && data[i].versions.length === 1) {
var normal = data[i].versions[0]
normalized[i] = {}
normalized[i][normal] = stats[i][versions[0]]
} else {
normalized[i] = stats[i]
}
}
return normalized
}
stats is untrusted: it comes from JSON.parse()-ing a
browserslist-stats.json file — auto-discovered by walking up the directory
tree from the project root on every browserslist() call, regardless of
the query (env.getStat(opts, browserslist.data) runs unconditionally
inside browserslist()) — or from opts.stats passed programmatically /
via the CLI's --stats= flag. data is browserslist.data, a plain object
populated only with real browser names.
Two independent bugs from the same root cause (unguarded for...in over
untrusted keys used with plain-object bracket access/assignment):
data[i] has no hasOwnProperty guard. If stats contains a
key that also happens to be an inherited Object.prototype member name —
"__proto__", "toString", "valueOf", "constructor",
"hasOwnProperty", "isPrototypeOf", etc. — data[i] resolves to that
inherited function/object (always truthy), and the code then does
data[i].versions.length → undefined.length → uncaught TypeError,
for any such key whose JSON value has exactly one sub-key, e.g.:
{ "toString": { "onekey": 5 }, "chrome": { "100": 50 } }
normalized[i] = ... on the fresh
normalized = {} — if i is exactly "__proto__" (and normalized has
no own property by that name yet), this computed assignment invokes the
real Object.prototype.__proto__ setter, changing normalized's actual
[[Prototype]] instead of creating a plain property.Because this runs on every browserslist() call regardless of the
query, simply committing a poisoned browserslist-stats.json anywhere in a
project's directory tree breaks every subsequent Browserslist call in that
project — including calls made by Autoprefixer, Babel preset-env,
Stylelint, or PostCSS internally, for completely unrelated queries.
browserslist-stats.json file anywhere between the project root and
filesystem root, containing e.g.
{"toString": {"onekey": 5}, "chrome": {"100": 50}}.browserslist()
internally, for any query.TypeError on the very first call.Confirmed crash (real browserslist() call, v4.28.6) with stats keys:
__proto__, toString, valueOf, hasOwnProperty, constructor,
isPrototypeOf — each paired with a one-key JSON object — for any query,
including browserslist('defaults') which never mentions stats.
var normalized = Object.create(null)
for (var i in stats) {
var versions = Object.keys(stats[i])
var known = Object.prototype.hasOwnProperty.call(data, i) && data[i]
if (versions.length === 1 && known && known.versions.length === 1) {
var normal = known.versions[0]
normalized[i] = Object.create(null)
normalized[i][normal] = stats[i][versions[0]]
} else {
normalized[i] = stats[i]
}
}
return normalized
normalized uses Object.create(null) so a write to "__proto__" is an
ordinary property set, never a [[Prototype]] change; data[i] is replaced
with an explicit hasOwnProperty check so it never resolves to an inherited
Object.prototype member.
Verification:
NODE_ENV=test npx uvu test .test.js → 301/301 pass unmodified
(test/custom.test.js, test/shareable-stats.test.js, test/cover.test.js
exercise the stats-handling paths).browserslist-stats.json + an unrelated browserslist('defaults') call)
now returns a normal result instead of crashing.opts.stats.opts.stats.browserslist @ HEAD (== v4.28.6, current latest stable release) under local Node.js v20.19.5. Pure JS library — executed directly, no server needed.
Found via a systematic review of prototype-pollution-adjacent patterns in
this codebase after confirming two unrelated algorithmic-complexity issues
(reported separately as GHSA-rrmg-cfrq-23vv and GHSA-g6p8-hj8g-x889) in the
same research pass. A similar for...in + bracket-write pattern in
index.js's copyObject() (used by normalizeAndroidData) was already
guarded against __proto__/constructor/prototype keys by a prior,
unrelated commit — that guard was never applied to this function.
Bạn đang dùng một trong các sản phẩm này? Quét site để biết có đang lộ phiên bản có lỗ hổng không.
Quét site miễn phí