티스토리 뷰

자바스크립트의 만능키(?) 그만큼 위험 부담도 큰 것이 eval이다. 사용을 자제하라는 게 일반적인 평이다.

그럼 어떻게 eval을 대응 혹은 대신할 것인지 알아보자.

왜 쓰면 안 되나

막연히 "위험하다"가 아니라 이유가 명확하다.

  • 보안 — 문자열에 사용자 입력이 섞이면 임의 코드 실행(XSS)이 된다. 서버라면 더 심각하다
  • 성능 — JS 엔진은 코드를 미리 최적화(JIT)하는데, eval은 런타임에 코드가 생기므로 최적화를 포기한다. 주변 스코프 최적화까지 무효화된다
  • 디버깅 — 스택 트레이스가 eval 내부로 찍혀서 어디가 문제인지 찾기 어렵다
  • 번들러 — 정적 분석이 불가능해서 트리 셰이킹이나 minify가 제대로 동작하지 않는다
  • CSP — Content-Security-Policy를 적용하면 eval은 기본적으로 차단된다

대체 방법 1: 함수 호출

기존의 코드

function say(){
    alert('say yo ~')
}

eval('say()');

대체 방법

function say(){
   alert('say you ~');
}

new Function('return say()')()

다만 솔직히 말하면 new Function도 근본적으로는 같은 문제를 갖는다. 문자열을 코드로 바꾸는 건 동일하기 때문이다. 다른 점은 전역 스코프에서 실행된다는 것 정도다. 지역 변수를 훔쳐보지 못하니 eval보다는 낫지만, CSP에서는 똑같이 막힌다.

가장 좋은 건 아예 문자열을 거치지 않는 것이다.

// 함수를 맵에 담아두고 키로 꺼내 쓴다
const actions = {
  say: () => alert('say you ~'),
  hide: () => console.log('hide'),
};

const name = 'say';
actions[name]?.();   // 등록된 것만 실행된다

이 방식은 안전하다. 맵에 없는 이름이 들어오면 그냥 아무 일도 안 일어난다. 임의 코드가 실행될 여지가 없다.

대체 방법 2: 객체 속성 동적 접근

기존 방법

var jsonObj = { lang : 'javascript' }
var objName = 'lang'
console.log(  eval("jsonObj." + objName)  )

권장 방법

var jsonObj = { lang : 'javascript' }
var objName = 'lang'

console.log( jsonObj[objName] )

이게 eval을 쓰는 가장 흔한 이유인데, 대괄호 표기법으로 완전히 대체된다. 성능도 비교할 수 없이 빠르고 안전하다.

중첩 경로 접근

eval('obj.a.b.c') 같은 깊은 경로가 필요하다면 이렇게 한다.

// 직접 구현
const getPath = (obj, path) =>
  path.split('.').reduce((acc, key) => acc?.[key], obj);

getPath({ a: { b: { c: 1 } } }, 'a.b.c');   // 1

// 또는 lodash
_.get(obj, 'a.b.c');

상황별 대체 정리

eval을 쓰려던 이유 대체 방법
객체 속성 동적 접근 obj[key]
JSON 문자열 파싱 JSON.parse()
함수 이름으로 호출 함수 맵 (actions[name]())
문자열 수식 계산 expr-eval, mathjs 같은 파서 라이브러리
동적 모듈 로딩 동적 import()
템플릿 문자열 치환 템플릿 리터럴 또는 치환 함수

JSON은 절대 eval로 파싱하지 않는다

// 위험 - 절대 금지
const data = eval('(' + jsonString + ')');

// 올바름
const data = JSON.parse(jsonString);

오래된 코드에서 아직도 보이는 패턴이다. 서버 응답에 코드가 섞여 있으면 그대로 실행된다. JSON.parse는 순수 데이터만 해석하므로 안전하다.

그래도 써야 한다면

사용자가 입력한 수식을 계산해야 하는 계산기 같은 경우처럼 정말 필요한 상황도 있다. 이때는 전용 파서를 쓴다.

import { Parser } from 'expr-eval';

const parser = new Parser();
parser.evaluate('2 * (3 + 4)');   // 14
// 함수 호출이나 객체 접근은 파서가 허용하지 않으므로 안전

수식만 해석하고 코드는 실행하지 않으므로 eval과 근본적으로 다르다.

정리

  • eval은 보안·성능·디버깅·CSP 전부에서 문제가 된다
  • new Function은 전역 스코프라는 점만 다를 뿐 근본 문제는 같다
  • 속성 접근은 obj[key], JSON은 JSON.parse
  • 함수를 이름으로 호출해야 하면 함수 맵을 만든다
  • 수식 계산이 필요하면 전용 파서 라이브러리
댓글


최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday