JavaScript에서 eval()을 대체할 수 있는 방법

JavaScript에서 eval()을 대체할 수 있는 방법

D
dongAuthor
5 min read

JavaScript의 eval()이 위험한 이유와 new Function()으로 안전하게 대체하는 방법을 실용적인 예제와 함께 알아보세요. 보안과 성능을 모두 잡는 팁!

JavaScript를 사용하다 보면 문자열로 된 코드를 실행해야 할 때가 있습니다. 이럴 때 가장 먼저 떠오르는 것이 eval() 함수죠. 하지만 eval()은 보안 취약점과 성능 문제로 악명이 높습니다.

이 글에서는 eval()의 위험성을 살펴보고, 더 안전한 대안인 new Function()을 어떻게 활용할 수 있는지 실용적인 예제와 함께 알아보겠습니다.

eval()이란 무엇인가?

MDN 문서에 따르면, eval()은 문자열로 표현된 JavaScript 코드를 실행하는 함수입니다. 간단한 예제를 통해 작동 방식을 살펴볼까요?

let a = 3;
let b = 5;
eval('a += ' + b + ' + 2');
console.log(a); // 10

위 코드에서 eval()은 문자열 'a += 5 + 2'를 실제 JavaScript 코드로 실행합니다. 얼핏 보면 편리해 보이지만, 이 단순함 뒤에는 심각한 문제들이 숨어 있어요.

eval()의 보안 위험성

eval()은 caller의 권한으로 코드를 실행합니다. 이게 무슨 의미일까요? 만약 사용자 입력값을 eval()로 실행한다면, 악의적인 코드가 그대로 실행될 수 있다는 뜻입니다.

var userContent = getUserInput(); // 사용자로부터 입력받은 값
eval(userContent); // 위험!

해커가 "window.location = 'http://malicious-site.com'"와 같은 코드를 입력하면 어떻게 될까요? 사용자는 악성 사이트로 리다이렉트되거나, 더 심각한 경우 민감한 데이터가 탈취될 수 있습니다.

또한 eval()은 호출된 스코프(scope)에 직접 접근할 수 있어요. 이는 로컬 변수를 읽거나 수정할 수 있다는 의미입니다:

function MyFunc() {
  let secretToken = "abc123";
  eval('console.log(secretToken); secretToken = "hacked";');
  console.log(secretToken); // "hacked"
}

eval()의 성능 문제

eval()은 JavaScript 인터프리터를 직접 호출하기 때문에 다른 대안들보다 느립니다. 현대 JavaScript 엔진들은 코드를 최적화하는데, eval()은 이러한 최적화를 방해합니다. 엔진이 어떤 코드가 실행될지 미리 알 수 없기 때문이죠.

new Function()으로 더 안전하게

new Function()eval()과 비슷하게 문자열로부터 함수를 생성하지만, 훨씬 더 안전합니다. 어떻게 사용하는지 살펴볼까요?

const add = new Function('a', 'b', 'return a + b');
console.log(add(2, 3)); // 5

문법은 다음과 같습니다:

let func = new Function([arg1, arg2, ...argN], functionBody);

여기서 중요한 점은 new Function()으로 생성된 함수는 항상 전역 스코프에서 실행된다는 것입니다. 로컬 변수에 접근할 수 없어요:

function MyFunc() {
  let b = 123;
  new Function('console.log(b);')(); // ReferenceError: b is not defined
}

이런 제약이 오히려 보안을 강화합니다. 악의적인 코드가 함수 내부의 민감한 변수에 접근하는 것을 원천적으로 차단하는 거죠.

eval()과 new Function() 비교하기

두 방식의 핵심 차이점을 정리해볼게요.

실행 컨텍스트와 스코프

eval()은 현재 실행 컨텍스트에서 코드를 평가합니다:

function testEval() {
  let x = 10;
  eval('console.log(x); x = 20;');
  console.log(x); // 20 - 변수가 수정됨
}
testEval();

반면 new Function()은 전역 컨텍스트에서만 실행됩니다:

function testFunction() {
  let x = 10;
  const func = new Function('console.log(typeof x);'); // "undefined"
  func();
  console.log(x); // 10 - 변수가 안전하게 보호됨
}
testFunction();

보안성

new Function()의 제한된 스코프 접근은 보안상 큰 장점입니다. 로컬 변수에 접근할 수 없으므로, 악의적인 코드가 실행되더라도 피해 범위가 제한적이에요.

실전 예제로 배우기

실제로 어떻게 eval()new Function()으로 대체할 수 있을까요?

간단한 수식 계산

// eval() 사용 (권장하지 않음)
const result1 = eval('2 + 3 * 4');

// new Function() 사용 (권장)
const calculate = new Function('return 2 + 3 * 4');
const result2 = calculate();

동적 함수 생성

// 사용자가 입력한 함수 본문으로 함수 생성
const functionBody = 'return x * 2';
const double = new Function('x', functionBody);
console.log(double(5)); // 10

외부 변수 안전하게 전달하기

전역 변수는 new Function()에서도 접근 가능하지만, 더 안전한 방법은 매개변수로 명시적으로 전달하는 것입니다:

const multiplier = 3;

// 매개변수로 명시적 전달
const multiply = new Function('x', 'multiplier', 'return x * multiplier');
console.log(multiply(5, multiplier)); // 15

JSON 파싱 대안

엄격한 JSON이 아닌 JavaScript 객체 리터럴을 파싱할 때도 활용할 수 있어요:

function looseJsonParse(obj) {
  return Function('"use strict";return (' + obj + ")")();
}

const result = looseJsonParse("{a:(4-1), b:function(){}, c:new Date()}");
console.log(result.a); // 3

언제 new Function()을 사용해야 할까?

new Function()이 유용한 시나리오들을 살펴볼게요:

동적 코드 생성

설정 파일이나 사용자 입력을 기반으로 함수를 동적으로 생성해야 할 때 유용합니다. 예를 들어, 사용자 정의 필터나 정렬 로직을 구현할 때죠.

샌드박스 환경

전역 스코프에서만 실행되기 때문에, 플러그인 시스템이나 사용자 스크립트 실행 환경을 만들 때 적합합니다.

성능이 중요한 경우

반복적으로 실행되는 코드라면, 한 번 new Function()으로 컴파일한 후 재사용하는 것이 eval()을 매번 호출하는 것보다 효율적입니다.

주의사항과 한계

new Function()도 완벽한 해결책은 아닙니다. 여전히 문자열로부터 코드를 생성하는 것이므로, 신뢰할 수 없는 입력은 철저히 검증해야 해요.

또한 일부 엣지 케이스에서는 eval()이나 new Function() 모두 적합하지 않을 수 있습니다. 가능하다면 다음과 같은 대안을 먼저 고려해보세요:

객체 속성 접근 시:

// eval() 사용 (나쁨)
const propName = 'username';
const value = eval('user.' + propName);

// 브라켓 표기법 사용 (좋음)
const value = user[propName];

이벤트 핸들러 등록 시:

// 인라인 문자열 (나쁨)
element.setAttribute("onclick", "handleClick()");

// 이벤트 리스너 (좋음)
element.addEventListener("click", handleClick);

타이머 함수 사용 시:

// 문자열 코드 (나쁨)
setTimeout("console.log('Hello')", 1000);

// 함수 전달 (좋음)
setTimeout(() => console.log('Hello'), 1000);

더 안전한 코드를 향해

eval()은 편리해 보이지만, 보안과 성능 측면에서 심각한 문제를 야기할 수 있습니다. new Function()은 전역 스코프 제한을 통해 이러한 위험을 크게 줄여주는 더 나은 대안이에요.

물론 가장 좋은 방법은 동적 코드 실행 자체를 피하는 것입니다. 하지만 불가피한 상황이라면, new Function()을 선택하되 입력값 검증을 철저히 하는 것을 잊지 마세요.

보안은 타협할 수 없는 영역입니다. 코드 한 줄이 전체 애플리케이션의 안전을 좌우할 수 있다는 점을 항상 기억하면서 개발하시길 바랍니다 :)