[JavaScript] eval 메서드에 대하여
Overview
자바스크립트에서 제공하고 있는 함수이지만 사용해서는 안되는 함수가 있습니다. 바로 eval 함수입니다.
이번 포스팅에서는 왜 eval을 사용하면 안된다고 하는지 살펴보도록 하겠습니다.
eval
eval 함수는 내장 전역 함수입니다. 즉 사용자가 정의한 함수가 아닌 자바스크립트 언어에서 제공하고 있는 함수입니다.
eval 함수는 문자열로 된 코드를 인자로 받습니다. 인자로 전달된 자바스크립트 코드가 식이면 런타임에 실행해 값을 생성하고, 문이라면 런타임에 코드를 실행합니다.
eval("1 + 2"); // 3
const value = eval("1 + 3"); // value에 4 할당
eval("var x = 1 * 6");
console.log(x);
간단한 코드를 살펴봅시다. 코드에서 value에 값을 할당하고 있습니다. eval 함수에 "1+3" 이라는 문자열을 전달하고, 런타임에 해당 코드를 실행해 value에 값을 할당합니다.
코드의 평가 시점에 x 라는 변수는 존재하지 않습니다. 따라서 x를 참조하려고 할 때 ReferenceError가 발생해야 할 것 같지만 eval 메서드를 통해 전달한 "var x = 1 * 6"이라는 문이 런타임에 실행이 됩니다. 따라서 x의 값은 6이라는 값이 출력이 됩니다.
eval 메서드를 통해 전달받은 코드는 스코프를 수정합니다. 다음의 예제 코드를 살펴봅시다.
var x = "Lee";
function foo() {
eval("var x = 3");
x += "Mong";
console.log(x);
}
foo();
foo 함수는 전역 변수인 x 변수에 문자열을 추가로 입력하고 싶어합니다. 출력 결과로 "LeeMong" 을 기대하고 있습니다.
하지만 eval로 전달한 코드로 런타임에 x 식별자가 선언이 되고 값이 할당됩니다. 따라서 출력 결과 값은 기대하지 못한 "3Mong" 이라는 값이 출력이 됩니다.

코드의 평가 시점에 생성된 스코프 체인에는 x라는 식별자가 전역 스코프에만 존재 했습니다. 하지만 코드의 실행 시점에 eval 메서드를 통해 전달 받은 코드가 실행이 됐고, foo 함수 스코프에 x라는 식별자가 스코프에 등록이 됩니다. 위와 같이 eval 메서드를 통해 전달한 코드는 스코프를 수정합니다.
strict mode
strict mode에서는 eval을 통해 실행된 코드는 별도의 eval 스코프를 갖고 있습니다. eval을 통해 전달된 코드가 실행이 되면서 eval 실행 컨텍스트를 생성하기 때문입니다.
위 예제 코드의 foo 함수에 strict mode를 적용합니다. 그 후 코드를 실행하면 기대한 출력 값인 "LeeMong"이 출력되는 것을 확인할 수 있습니다. 실행 컨텍스트 관점에서 생각해본다면 foo 함수 실행 컨텍스트에 x라는 식별자가 등록이 되는 것이 아닌, eval 실행 컨텍스트에 x라는 값이 바인딩 됐기 때문입니다. 따라서 15행에서 x는 상위 스코프인 전역 스코프에서 x 식별자를 검색하고 기대한 출력 값을 확인할 수 있습니다.

보안
우리는 웹 애플리케이션을 만들려고 자바스크립트를 사용하고 있습니다. 만약 사용자에게 입력 받은 값을 eval 메서드를 활용해 처리한다면 어떤 일이 발생할까요?
아래의 예시 코드를 통해서 살펴봅시다!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<input id="input" />
<button id="submit">submit</button>
<p id="result"></p>
</body>
<script>
var inputValue = "";
var resultValue = "";
const input = document.getElementById("input");
const submitButton = document.getElementById("submit");
const result = document.getElementById("result");
input.addEventListener("change", (event) => {
inputValue = eval(event.currentTarget.value);
resultValue = inputValue * 5;
});
submitButton.addEventListener("click", (event) => {
result.innerText = resultValue;
});
</script>
</html>
우리는 간단하게 사용자에게 값을 입력받아서 5를 곱한 값을 화면에 보여주고 싶습니다.
이런 코드를 작성할 일은 없겠지만 eval 메서드의 위험성을 모른채 eval 함수를 통해 값을 inputValue에 넣어주고 있다고 가정합시다!

숫자를 입력하면 자연스럽게 우리가 의도한 값이 표현됩니다. 하지만 갑자기 나쁜 마음을 먹은 사용자가 이 기능을 망치고 싶은 생각이 들었습니다! 개발자 도구를 통해 소스코드를 살펴보니 input의 값을 eval 메서드로 처리하고 있다는 것을 확인합니다.

사용자는 "var inputValue = "not a number!! haha"; submitButton.innerText = "haha";" 라는 코드를 input에 입력을 합니다. 우리의 애플리케이션은 기존의 기능을 잃고 submit 버튼의 이름도 바뀌게 됩니다.

정말 가벼운 예시를 들었습니다. 하지만 만약 우리의 웹 애플리케이션이 토큰 관리에 신경을 쓰지 않아 토큰이 탈취당한 상태라면 서버에 탈취한 토큰으로 악의적인 요청을 할 수 있습니다. 이는 정말 치명적인 웹 취약점 중에 하나입니다.
마치며
위의 가벼운 케이스로도 eval 메서드의 위험도를 느끼실 수 있을거라고 생각합니다.
2023년도 Deview에 참가했을때 모 기업의 엔지니어 분의 프론트엔드 세션을 들었는데, eval 메서드를 런타임에 애플리케이션을 주입하는 용도로 사용하고 있다는 컨퍼런스 내용을 들었습니다. 위험성이 있는 메서드 이지만 특정 기능을 구현하기 위해 해당 메서드를 사용할수도 있겠구나 싶었지만, 한편으로는 아리송한 생각도 들었습니다. 아키텍처 상 위험도가 없을수도 있겠지만, 절대 100%를 가정하면 안된다고 생각했기 때문입니다.
정말 어쩔수 없이 eval 메서드를 사용해야만 한다면 반드시 해당 메서드가 왜 위험한지 알고 사용하셨으면 좋겠습니다.