JavaScript / eval() 사용법과 보안 위험, 대체 방법

eval()은 문자열을 JavaScript 코드로 해석하고 실행한 뒤 그 결과를 반환합니다. eval("2+2")는 숫자 4입니다. 하지만 사용자 입력을 넣으면 입력이 계산식이 아니라 임의의 코드로 실행될 수 있으므로, 새 코드에서는 목적에 맞는 파서나 명시적인 함수 선택을 사용해야 합니다.

eval()의 기본 동작

eval(source)에 문자열을 전달하면 JavaScript 문법으로 파싱해 실행합니다. 아래의 고정된 예제는 결과가 4이지만, 같은 함수를 외부 입력에 적용하는 순간 문자열의 의미를 개발자가 통제하기 어려워집니다.

console.log(eval('2 + 2')); // 4
console.log('2 + 2');       // '2 + 2' 

문자열이 JavaScript 문법에 맞지 않으면 SyntaxError가 날 수 있습니다. eval()은 단순히 문자열에서 숫자를 읽는 함수가 아닙니다. MDN의 eval() 문서에서 실행과 반환 규칙을 확인할 수 있습니다.

사용자 입력을 eval()에 넣으면 안 되는 이유

입력 창에 적은 값이 코드로 실행되면 의도한 계산 범위를 넘어 페이지의 데이터나 기능에 접근할 수 있습니다. 이는 코드 삽입과 교차 사이트 스크립팅(XSS) 위험을 만듭니다. 또 동적 파싱은 성능과 디버깅에 불리하고, 사이트의 콘텐츠 보안 정책(CSP)에 의해 차단될 수도 있습니다.

계산기라면 허용하는 연산자만 고르고 숫자를 검증해야 합니다. 서버에서 받은 JSON을 읽으려는 목적이라면 JSON.parse()가 문법을 제한해 데이터를 읽습니다. 다만 JSON을 파싱한 뒤에도 데이터 구조와 값의 유효성은 별도로 확인해야 합니다.

작업별 대체 방법

하려는 일 권장 방법
JSON 문자열에서 데이터 읽기 JSON.parse(text)
문자열 키로 객체 속성 읽기 object[key] (허용 키 검증 포함)
몇 가지 연산 중 하나 실행 연산 이름과 함수를 명시적으로 연결한 객체 또는 switch

예를 들어 JSON.parse("{"count":2}")는 객체 데이터를 만듭니다. MDN의 JSON.parse() 문서는 JSON이 유효하지 않을 때의 오류도 설명합니다. 동적 속성 접근에는 허용할 키를 제한하고, 함수를 선택할 때는 선택 가능한 이름만 화면에 제공하세요.

전체 예제: eval 없이 사칙연산하기

다음 코드를 calculator-without-eval.html로 저장해 브라우저에서 엽니다. 두 수와 연산을 고르면 미리 정의한 함수 하나만 실행됩니다. 빈 값·유한하지 않은 값·0으로 나누기를 확인한 뒤 결과를 표시합니다.

<!doctype html>
<html lang="ko">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>eval 없이 계산하기</title>
</head>
<body>
<label for="first">첫 번째 수</label>
<input id="first" type="number" value="2">
<label for="operation">연산</label>
<select id="operation">
  <option value="add">더하기</option>
  <option value="subtract">빼기</option>
  <option value="multiply">곱하기</option>
  <option value="divide">나누기</option>
</select>
<label for="second">두 번째 수</label>
<input id="second" type="number" value="2">
<button id="calculate" type="button">계산</button>
<p id="result" aria-live="polite"></p>
<script>
const operations = {
  add: (a, b) => a + b,
  subtract: (a, b) => a - b,
  multiply: (a, b) => a * b,
  divide: (a, b) => a / b
};

document.querySelector('#calculate').addEventListener('click', () => {
  const firstText = document.querySelector('#first').value.trim();
  const secondText = document.querySelector('#second').value.trim();
  const first = Number(firstText);
  const second = Number(secondText);
  const operation = document.querySelector('#operation').value;
  const result = document.querySelector('#result');

  if (!firstText || !secondText || !Number.isFinite(first) || !Number.isFinite(second)) {
    result.textContent = '유한한 숫자 두 개를 입력하세요.';
    return;
  }
  if (operation === 'divide' && second === 0) {
    result.textContent = '0으로 나눌 수 없습니다.';
    return;
  }
  result.textContent = `결과: ${operations[operation](first, second)}`;
});
</script>
</body>
</html>

기본값 2와 2에서 “계산”을 누르면 4가 나옵니다. 연산을 “나누기”로 바꾸고 두 번째 수를 0으로 입력하면 오류 문구가 나옵니다. 빈 입력도 계산하지 않습니다. 실제 서비스에서 소수 자리수나 큰 수의 정밀도가 중요하면 Number의 부동소수점 특성까지 고려해야 합니다.