본문으로 건너뛰기
개발 머꼬
개발 노트JavaScript
hohyeon.dev28

사용자가 적은 이름을 in으로 검사했더니 constructor와 __proto__가 통과한 이유

  • #Engineering Note
  • #JavaScript
  • #Security

문제 발생

댓글의 코드 펜스에 적힌 언어 이름이 지원 목록에 있는지 확인한 뒤 하이라이터에 넘기고 있었습니다.

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 같은 함수이니, 그것을 받은 라이브러리가 죽는 것이 당연합니다.

이 검사는 사용자 입력이 라이브러리로 들어가는 경계였습니다. 경계에서 통과시키는 기준이 "어딘가에 그런 이름이 있다"였던 것이 문제입니다.

해결 방안

  1. 자기 속성만 검사합니다. MDN은 Object.hasOwn()Object.prototype.hasOwnProperty()의 대체로 소개합니다. 상속된 속성이거나 없는 속성이면 false입니다.
const lang = Object.hasOwn(bundledLanguages, language) ? language : "text";
  1. obj.hasOwnProperty(key) 대신 Object.hasOwn을 씁니다. 프로토타입이 없는 객체(Object.create(null))에는 hasOwnProperty 메서드 자체가 없어 호출이 실패하고, 객체가 같은 이름의 속성을 덮어썼을 수도 있습니다. Object.hasOwn은 정적 함수라 두 경우 모두 안전합니다.

  2. 조회 테이블은 애초에 Map이나 Set으로 둡니다. set.has(key)는 프로토타입을 보지 않으므로 이 종류의 실수가 생길 자리가 없습니다.

const languages = new Set(Object.keys(bundledLanguages));
const lang = languages.has(language) ? language : "text";
  1. 같은 패턴을 다른 곳에서도 찾습니다. 본문 마크다운의 코드 블록, 설정 키 조회, switch 대신 객체로 만든 디스패치 테이블이 흔한 자리입니다. 실제로 확인할 때는 constructor를 넣어 봅니다 — 한 줄로 재현됩니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.