Kestrel
대시보드로 돌아가기
CVE-2026-110910LOW· 3.7MITRENVD대응게시일: 2026. 10. 27.수정일: 2026. 10. 27.CNA: 7ffcee3d-2c14-4c3e-b844-86c6a321a158Received

### Summary `qs.stringify` with `allowEmptyArrays: true` writes the key of an empty array without encoding it, even though keys are encod

위협 신호 · CVSS · EPSS · KEV

정기 패치· 높은 악용 신호 없음
CVSS
3.7low

이론적 심각도 점수

EPSS
—

예측 데이터 없음

KEV
미등재

실측 악용 기록 없음

권장 대응 기한60일 이내CISA SSVC 기준

계획된 패치 주기 내 조치(60일 이내)

외부 노출· KEV 미등재 · 자동화 어려움 · 부분 영향 · 외부 노출

CVSS 벡터 · 메트릭

악용 경로
공격 벡터네트워크
공격 복잡도높음
필요 권한불필요
사용자 상호작용불필요
범위불변
영향
기밀성 영향없음
무결성 영향낮음
가용성 영향없음
버전별 점수
CVSS 4.06.3MEDIUM· 악용성숙도 NOT_DEFINED
CVSS 3.13.7LOW· 악용성 2.2· 영향도 1.4
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N

약점 (CWE)

상세 설명

Summary

qs.stringify with allowEmptyArrays: true writes the key of an empty array without encoding it, even though keys are encoded by default. A key that contains & (or the configured delimiter), =, or # ∴ becomes query string syntax instead of data, so a caller-supplied key name can add parameters to the serialized query string, or cut it short.

Details

In lib/stringify.js, the allowEmptyArrays branch returns adjustedPrefix + '[]' early, before the code that passes every other key through encoder and formatter. So { "x&k=v": ["1"] } serializes as x%26k%3Dv%5B0%5D=1, but { "x&k=v": [] } serializes as x&k=v[]. The early return applies at any depth ({ "a": { "b&k=v": [] } } gives a[b&k=v][]), under every arrayFormat, with allowDots, and with a custom encoder, which is never called for that key. With encode: false or encodeValuesOnly: true, keys are intentionally left unencoded on every path; those configurations are not affected.

PoC
text
1
2
3
4var qs = require("qs");
5
6
7
8var query = qs.stringify({ q: "hi", "x&apiKey=evil&y": [] }, { allowEmptyArrays: true });
9
10
11
12// "q=hi&x&apiKey=evil&y[]"
13
14
15
16qs.parse(query);
17
18
19
20// { q: "hi", x: "", apiKey: "evil", y: [""] }
21
22
23
24qs.stringify({ "x#": [], z: "1" }, { allowEmptyArrays: true });
25
26
27
28// "x#[]&z=1": in a URL, everything after "#" becomes the fragment
29
30
Fix

Applied in 234d505 on main and released as v6.17.0: the key of an empty array goes through encoder and formatter exactly as the key of any other value does, so { "x&apiKey=evil&y": [] } now serializes as x%26apiKey%3Devil%26y[]. Keys that need no encoding serialize as before, with two exceptions that match every other path: the brackets of a nested key are now encoded (a[b][] becomes a%5Bb%5D[], which qs.parse reads identically), and with encodeDotInKeys, a dot in the key becomes %252E instead of %2E (which decodeDotInKeys previously misread as nesting).

Affected versions

>=6.12.0 <6.17.0, fixed in v6.17.0.

allowEmptyArrays was introduced in f22b2bc and first shipped in v6.12.0, with this early return. Every release from v6.12.0 through v6.16.1 is affected. Earlier versions have no allowEmptyArrays option and are not affected.

Impact

An application that passes an object whose key names come from untrusted input to qs.stringify with allowEmptyArrays: true (for example, to forward a parsed JSON body or user-selected fields to an upstream service) can have parameters injected into the resulting query string. The injected parameters are not keys of the object, so deleting or overwriting a key on the object (such as setting a trusted apiKey after copying the user's fields) does not remove them; whether the attacker's value or the application's wins depends on how the receiving side handles a duplicate parameter. A # in such a key ends the query string early when the result is placed in a URL. allowEmptyArrays is off by default, and only keys whose value (after any filter) is an empty array are affected.

AI 심층 분석

공격 시나리오 · 재현 가능한 PoC 페이로드 · 즉시 적용 가능한 차단 패치를 한 번에 받아 보세요. 보안 운영팀이 그대로 점검·티켓팅에 쓸 수 있는 형태로 정리해 드립니다.