사용자가 적은 이름을 in으로 검사했더니 constructor와 __proto__가 통과한 이유
문제 발생
댓글의 코드 펜스에 적힌 언어 이름이 지원 목록에 있는지 확인한 뒤 하이라이터에 넘기고 있었습니다.
const lang = language in bundledLanguages ? language : "text";
const html = await codeToHtml(code, { lang });그런데 ```constructor나 ```__proto__, ```toString을 적은 댓글이 들어오자 하이라이터가 예외를 던졌고, 그 댓글이 달린 글 전체가 500이 됐습니다. 댓글 하나로 남의 글을 아무나 못 열게 만들 수 있는 상태였습니다.
원인 분석
in은 자기 속성만 보지 않습니다. MDN은 in 연산자를 이렇게 정의합니다 — 지정한 속성이 지정한 객체 또는 그 프로토타입 체인에 있으면 true를 돌려준다고요.
bundledLanguages는 평범한 객체라 Object.prototype을 상속합니다. 그래서 "constructor" in bundledLanguages는 언어 목록과 무관하게 true입니다. toString, hasOwnProperty, __proto__도 마찬가지입니다. 검사는 통과했는데 실제 값은 언어 정의가 아니라 Object.prototype.constructor 같은 함수이니, 그것을 받은 라이브러리가 죽는 것이 당연합니다.
이 검사는 사용자 입력이 라이브러리로 들어가는 경계였습니다. 경계에서 통과시키는 기준이 "어딘가에 그런 이름이 있다"였던 것이 문제입니다.
해결 방안
- 자기 속성만 검사합니다. MDN은
Object.hasOwn()을Object.prototype.hasOwnProperty()의 대체로 소개합니다. 상속된 속성이거나 없는 속성이면 false입니다.
const lang = Object.hasOwn(bundledLanguages, language) ? language : "text";-
obj.hasOwnProperty(key)대신Object.hasOwn을 씁니다. 프로토타입이 없는 객체(Object.create(null))에는hasOwnProperty메서드 자체가 없어 호출이 실패하고, 객체가 같은 이름의 속성을 덮어썼을 수도 있습니다.Object.hasOwn은 정적 함수라 두 경우 모두 안전합니다. -
조회 테이블은 애초에
Map이나Set으로 둡니다.set.has(key)는 프로토타입을 보지 않으므로 이 종류의 실수가 생길 자리가 없습니다.
const languages = new Set(Object.keys(bundledLanguages));
const lang = languages.has(language) ? language : "text";- 같은 패턴을 다른 곳에서도 찾습니다. 본문 마크다운의 코드 블록, 설정 키 조회,
switch대신 객체로 만든 디스패치 테이블이 흔한 자리입니다. 실제로 확인할 때는constructor를 넣어 봅니다 — 한 줄로 재현됩니다.
댓글0
댓글을 남기려면 로그인이 필요해요. 로그인
아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.