### Резюме
Когда `qs.parse` вызывается с `comma: true` и `throwOnLimitExceeded: true`, значение, разделенное запятыми, под ключом в скобках (`a[]=1,2,3,4`) разбивается на массив без сравнения с `arrayLimit`, в то время как то же самое значение под плоским ключом (`a=1,2,3,4`), индексированным ключом (`a[0]=`), вложенным ключом (`a[b]=`) или ключ с точкой (`a.b=` с `allowDots`) выдает документированную `RangeError`. Таким образом, один параметр, такой как `a[]=1,2,2,...`, создает внутренний массив произвольной длины, даже если вызывающая сторона выбрала жесткое ограничение. Это форма ключа `[]=`, которую не охватывает исправление CVE-2026-2391 (qs 6.14.2).
### Подробности
В `lib/parse.js` значение, разделенное запятыми, под ключом `[]=` разделяется, а затем оборачивается как один вложенный элемент (`val = [val]`, так что каждая группа `a[]=x,y` считается одним элементом внешнего массива.
Проверка `arrayLimit`, добавленная в 6.14.2 для значений запятых, выполняется после этого переноса, поэтому для частей `[]=` она всегда видела только обертку длины 1. 6.15.3 добавил счетчик запятых с предварительным разделением, чтобы выбрасывалось слишком большое значение перед его выделением, но заблокировал его флагом `isFlatArrayValue`, который `parseValues` установил в `false` для любой части, содержащей `[]=` и не передал его для объектно-значного ввода, поэтому пробел остался.
#### PoC
```js
вар qs = require('qs');
вар параметры = {запятая: правда, arrayLimit: 3, throwOnLimitExceeded: правда};
qs.parse('a=1,2,3,4', options); // Ошибка диапазона: превышен предел массива. В массиве допускается только 3 элемента.
qs.parse('a[]=1,2,3,4', options); // { a: [ [ '1', '2', '3', '4' ] ] } (без броска)
qs.parse('a[]=' + '1,'.repeat(1000000) + '1', { запятая: true, arrayLimit: 20, throwOnLimitExceeded: true });
// нет броска; выделяется внутренний массив из 1 000 001 элемента
```
#### Исправить
`lib/parse.js`, примененный в 8859c37 на `main` и выпущенный как v6.16.0: элемент `isFlatArrayValue` удален, поэтому каждое значение, разделенное запятой, учитывается в `arrayLimit` перед разделением независимо от формы ключа. Группа in-limit под `a[]=` по-прежнему считается одним элементом внешнего массива, а путь по умолчанию (`throwOnLimitExceeded: false`) не изменяется.
### Затронутые версии
`>=6.14.2 <6.16.0`, исправлено в версии 6.16.0.
В версии 6.14.2 введено принудительное применение `arrayLimit` для значений запятых (исправление для CVE-2026-2391), но только для значений, не находящихся под ключом `[]=`, и в каждой версии, начиная с v6.14.2 до v6.15.3, есть такой же пробел. v6.14.0 и v6.14.1, где `throwOnLimitExceeded` существует, но не применяется к какой-либо форме с запятой, подпадают под действие CVE-2026-2391, а не этой записи. В более ранних строках (с 6.7.x по 6.13.x) есть запятая, но нет `throwOnLimitExceeded`, поэтому нет жесткого ограничения для обходного пути с запятыми; в версиях до 6.7.0 опция «запятая» отсутствует.
### Влияние
Неаутентифицированный злоумышленник, который может получить доступ к приложению, которое анализирует ненадежные строки запроса или urlencoded тела с помощью `comma: true` и `throwOnLimitExceeded: true` (оба не по умолчанию), может обойти настроенное ограничение с помощью одного параметра `a[]=` и заставить анализатор выделить массив, пропорциональный размеру запроса. Стоимость строго линейна в байтах, предоставленных злоумышленником (около 0,1 микросекунды и от 6 до 7 сохраненных байт на входной байт; тот же порог нехватки памяти, что и задокументированный путь по умолчанию `throwOnLimitExceeded: false`), поэтому запрос транспортного уровня или ограничение размера тела полностью ограничивают его (а максимальный размер HTTP-заголовка узла по умолчанию в 16 КБ уже ограничивает строку запроса, поэтому требуются многомегабайтные полезные нагрузки). парсер тела).
В результате жесткое ограничение согласия не открывается при написании одного ключа, а не при неограниченном выделении из небольшого ввода.
Показать оригинальное описание (EN)
### Summary When `qs.parse` is called with `comma: true` and `throwOnLimitExceeded: true`, a comma-separated value under a bracket-push key (`a[]=1,2,3,4`) is split into an array without being compared against `arrayLimit`, while the same value under a flat key (`a=1,2,3,4`), an indexed key (`a[0]=`), a nested key (`a[b]=`), or a dotted key (`a.b=` with `allowDots`) throws the documented `RangeError`. A single parameter such as `a[]=1,2,2,...` therefore produces an inner array of arbitrary length even though the caller opted into the hard limit. This is the `[]=` key form that the fix for CVE-2026-2391 (qs 6.14.2) did not cover. ### Details In `lib/parse.js`, a comma-separated value under a `[]=` key is split and then wrapped as a single nested element (`val = [val]`, so that each `a[]=x,y` group counts as one element of the outer array). The `arrayLimit` check that 6.14.2 added for comma values runs after that wrap, so for `[]=` parts it only ever saw the wrapper of length 1. 6.15.3 added a pre-split comma count so that an oversized value throws before it is allocated, but gated it on an `isFlatArrayValue` flag that `parseValues` set to `false` for any part containing `[]=`, and did not pass it for object-valued input, so the gap remained. #### PoC ```js var qs = require('qs'); var options = { comma: true, arrayLimit: 3, throwOnLimitExceeded: true }; qs.parse('a=1,2,3,4', options); // RangeError: Array limit exceeded. Only 3 elements allowed in an array. qs.parse('a[]=1,2,3,4', options); // { a: [ [ '1', '2', '3', '4' ] ] } (no throw) qs.parse('a[]=' + '1,'.repeat(1000000) + '1', { comma: true, arrayLimit: 20, throwOnLimitExceeded: true }); // no throw; a 1,000,001-element inner array is allocated ``` #### Fix `lib/parse.js`, applied in 8859c37 on `main` and released as v6.16.0: the `isFlatArrayValue` gate is removed, so every comma-split value is counted against `arrayLimit` before splitting regardless of key form. An in-limit group under `a[]=` still counts as one element of the outer array, and the default (`throwOnLimitExceeded: false`) path is unchanged. ### Affected versions `>=6.14.2 <6.16.0`, fixed in v6.16.0. v6.14.2 introduced `arrayLimit` enforcement for comma values (the fix for CVE-2026-2391) but only for values not under a `[]=` key, and every release from v6.14.2 through v6.15.3 has the same gap. v6.14.0 and v6.14.1, where `throwOnLimitExceeded` exists but does not apply to any comma form, are covered by CVE-2026-2391 rather than this record. Earlier lines (6.7.x through 6.13.x) have `comma` but no `throwOnLimitExceeded`, so there is no hard cap on any comma path to bypass; releases before 6.7.0 have no `comma` option. ### Impact An unauthenticated attacker who can reach an application that parses untrusted query strings or urlencoded bodies with both `comma: true` and `throwOnLimitExceeded: true` (both non-default) can bypass the configured limit with a single `a[]=` parameter and force the parser to allocate an array proportional to the request size. The cost is strictly linear in the attacker-supplied bytes (about 0.1 microseconds and 6 to 7 retained bytes per input byte; the same out-of-memory threshold as the documented default `throwOnLimitExceeded: false` path), so a transport-layer request or body size limit bounds it completely (and node's default maximum HTTP header size of 16 KB already bounds the request line, so multi-megabyte payloads need a body parser). The impact is that an opt-in hard limit fails open on one key spelling, not unbounded allocation from a small input.
Характеристики атаки
Последствия
Строка CVSS v4.0