June 29, 2025

πŸ’€ eval() λ˜λŠ” new Function()으둜 동적 μ½”λ“œ μ‹€ν–‰ν•˜κΈ°: λ³΄μ•ˆκ³Ό μ„±λŠ₯의 무덀

λ³΄μ•ˆ
JavaScript
μ„±λŠ₯
μ—λŸ¬μ²˜λ¦¬

Summary

eval() 및 new Function()은 λ¬Έμžμ—΄μ„ μ½”λ“œλ‘œ μ‹€ν–‰ν•˜μ—¬ μ›Ή μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ— μ‹¬κ°ν•œ λ³΄μ•ˆ 취약점(XSS)을 μ•ΌκΈ°ν•˜κ³ , μ„±λŠ₯ μ €ν•˜ 및 λ””λ²„κΉ…μ˜ 어렀움을 μ΄ˆλž˜ν•©λ‹ˆλ‹€. λŒ€μ‹  JSON.parse(), ν…œν”Œλ¦Ώ μ—”μ§„, λ˜λŠ” ν•¨μˆ˜ 콜백과 같은 μ•ˆμ „ν•˜κ³  효율적인 λŒ€μ•ˆμ„ μ‚¬μš©ν•˜μ—¬ λ³΄μ•ˆκ³Ό μ½”λ“œ ν’ˆμ§ˆμ„ 확보해야 ν•©λ‹ˆλ‹€.

Why Wrong?

1. μ‹¬κ°ν•œ λ³΄μ•ˆ 취약점 (XSS)

eval() ν•¨μˆ˜λŠ” 인수둜 μ „λ‹¬λœ λ¬Έμžμ—΄μ„ JavaScript μ½”λ“œλ‘œ ν•΄μ„ν•˜κ³  μ‹€ν–‰ν•©λ‹ˆλ‹€. μ΄λŠ” μ‚¬μš©μž μž…λ ₯κ³Ό 같은 μ‹ λ’°ν•  수 μ—†λŠ” μ†ŒμŠ€μ˜ λ¬Έμžμ—΄μ„ eval()에 전달할 경우, κ³΅κ²©μžκ°€ μž„μ˜μ˜ μ½”λ“œλ₯Ό μ›Ή μ• ν”Œλ¦¬μΌ€μ΄μ…˜ λ‚΄μ—μ„œ μ‹€ν–‰ν•  수 μžˆλŠ” XSS(Cross-Site Scripting) μ·¨μ•½μ μœΌλ‘œ μ΄μ–΄μ§ˆ 수 μžˆμŠ΅λ‹ˆλ‹€. 예λ₯Ό λ“€μ–΄, μ•…μ„± 슀크립트λ₯Ό μ£Όμž…ν•˜μ—¬ μ‚¬μš©μž μ„Έμ…˜μ„ νƒˆμ·¨ν•˜κ±°λ‚˜ λ―Όκ°ν•œ 정보λ₯Ό μœ μΆœν•  수 μžˆμŠ΅λ‹ˆλ‹€.

new Function() μƒμ„±μž μ—­μ‹œ μœ μ‚¬ν•œ λ³΄μ•ˆ μœ„ν—˜μ„ λ‚΄ν¬ν•©λ‹ˆλ‹€. λ¬Έμžμ—΄λ‘œ ν•¨μˆ˜ 본문을 μ •μ˜ν•˜κΈ° λ•Œλ¬Έμ—, μ‹ λ’°ν•  수 μ—†λŠ” μ†ŒμŠ€μ˜ λ¬Έμžμ—΄μ„ μ‚¬μš©ν•˜λ©΄ eval()κ³Ό λ™μΌν•œ 곡격 벑터가 λ°œμƒν•©λ‹ˆλ‹€.

2. μ„±λŠ₯ μ €ν•˜ 및 μ΅œμ ν™” λ°©ν•΄

JavaScript 엔진은 μ½”λ“œλ₯Ό 미리 νŒŒμ‹±ν•˜κ³  μ΅œμ ν™”ν•˜μ—¬ μ‹€ν–‰ 속도λ₯Ό λ†’μž…λ‹ˆλ‹€. ν•˜μ§€λ§Œ eval()μ΄λ‚˜ new Function()으둜 λ™μ μœΌλ‘œ μƒμ„±λœ μ½”λ“œλŠ” λŸ°νƒ€μž„μ— νŒŒμ‹± 및 컴파일 과정을 거쳐야 ν•˜λ―€λ‘œ, 미리 μ΅œμ ν™”λœ μ½”λ“œλ³΄λ‹€ 훨씬 느리게 싀행될 수 μžˆμŠ΅λ‹ˆλ‹€. λ˜ν•œ, eval()은 ν˜ΈμΆœλ˜λŠ” μŠ€μ½”ν”„λ₯Ό λ³€κ²½ν•  수 μžˆλŠ” νŠΉμ„± λ•Œλ¬Έμ—, JIT(Just-In-Time) μ»΄νŒŒμΌλŸ¬κ°€ ν•΄λ‹Ή μŠ€μ½”ν”„μ˜ λ³€μˆ˜λ‚˜ ν•¨μˆ˜λ₯Ό μ΅œμ ν™”ν•˜λŠ” 것을 λ°©ν•΄ν•˜μ—¬ 전체 μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ μ„±λŠ₯에도 μ•…μ˜ν–₯을 λ―ΈμΉ  수 μžˆμŠ΅λ‹ˆλ‹€.

3. λ””λ²„κΉ…μ˜ 어렀움

λ™μ μœΌλ‘œ μƒμ„±λœ μ½”λ“œλŠ” μ†ŒμŠ€ λ§΅(Source Map) 지원이 μ–΄λ ΅κ±°λ‚˜ λΆˆκ°€λŠ₯ν•˜μ—¬, μ˜ˆμƒμΉ˜ λͺ»ν•œ 였λ₯˜κ°€ λ°œμƒν–ˆμ„ λ•Œ λ””λ²„κΉ…ν•˜κΈ°κ°€ 맀우 μ–΄λ ΅μŠ΅λ‹ˆλ‹€. μŠ€νƒ νŠΈλ ˆμ΄μŠ€μ—μ„œ eval code λ˜λŠ” anonymous λ“±μœΌλ‘œ ν‘œμ‹œλ˜μ–΄ 문제의 근원을 νŒŒμ•…ν•˜κΈ° νž˜λ“€μ–΄μ§‘λ‹ˆλ‹€.

4. CSP(Content Security Policy) μœ„λ°˜

λŒ€λΆ€λΆ„μ˜ ν˜„λŒ€ μ›Ή μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ€ XSS 곡격을 μ™„ν™”ν•˜κΈ° μœ„ν•΄ μ—„κ²©ν•œ CSPλ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€. eval()μ΄λ‚˜ new Function() 같은 동적 μ½”λ“œ μ‹€ν–‰ λ©”μ„œλ“œλŠ” CSP의 unsafe-eval μ§€μ‹œμ–΄λ₯Ό ν•„μš”λ‘œ ν•˜λ©°, μ΄λŠ” λ³΄μ•ˆ 정책을 μ•½ν™”μ‹œν‚΅λ‹ˆλ‹€. κ°•λ ₯ν•œ CSPλŠ” μ΄λŸ¬ν•œ 동적 μ½”λ“œ 싀행을 기본적으둜 μ°¨λ‹¨ν•˜μ—¬ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ΄ μž‘λ™ν•˜μ§€ μ•Šκ²Œ λ§Œλ“€ 수 μžˆμŠ΅λ‹ˆλ‹€.

How to Fix?

1. JSON.parse() ν™œμš© (JSON 데이터 처리 μ‹œ)

eval()의 κ°€μž₯ ν”ν•œ 였용 사둀 쀑 ν•˜λ‚˜λŠ” JSON λ¬Έμžμ—΄μ„ JavaScript 객체둜 λ³€ν™˜ν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€. 이 경우 λ°˜λ“œμ‹œ JSON.parse()λ₯Ό μ‚¬μš©ν•΄μ•Ό ν•©λ‹ˆλ‹€. JSON.parse()λŠ” JSON ν‘œμ€€μ— λ§žλŠ” λ¬Έμžμ—΄λ§Œ μ•ˆμ „ν•˜κ²Œ νŒŒμ‹±ν•˜λ©°, JavaScript μ½”λ“œλ‘œ ν•΄μ„ν•˜μ§€ μ•Šμ•„ λ³΄μ•ˆ μœ„ν—˜μ΄ μ—†μŠ΅λ‹ˆλ‹€.

2. ν…œν”Œλ¦Ώ μ—”μ§„ λ˜λŠ” ν”„λ ˆμž„μ›Œν¬μ˜ λ Œλ”λ§ κΈ°λŠ₯ ν™œμš© (동적 UI/HTML 생성 μ‹œ)

λ™μ μœΌλ‘œ UIλ₯Ό 생성해야 ν•œλ‹€λ©΄, React의 JSX, Vue의 ν…œν”Œλ¦Ώ, λ˜λŠ” Handlebars, Pug와 같은 μ•ˆμ „ν•œ ν…œν”Œλ¦Ώ 엔진을 μ‚¬μš©ν•˜μ„Έμš”. 이듀은 μ‚¬μš©μž μž…λ ₯을 μžλ™μœΌλ‘œ μ΄μŠ€μΌ€μ΄ν”„ μ²˜λ¦¬ν•˜μ—¬ XSS 곡격을 λ°©μ§€ν•˜κ³ , μ½”λ“œμ™€ λ°μ΄ν„°μ˜ 뢄리λ₯Ό λͺ…ν™•νžˆ ν•˜μ—¬ μœ μ§€λ³΄μˆ˜μ„±μ„ λ†’μž…λ‹ˆλ‹€.

3. ν•¨μˆ˜ λ§€κ°œλ³€μˆ˜ λ˜λŠ” μ„€μ • 객체 μ‚¬μš© (동적 둜직 κ΅¬ν˜„ μ‹œ)

동적인 λ™μž‘μ΄ ν•„μš”ν•˜λ‹€λ©΄, λ¬Έμžμ—΄μ„ μ½”λ“œλ‘œ μ‹€ν–‰ν•˜λŠ” λŒ€μ‹  ν•¨μˆ˜λ₯Ό λ§€κ°œλ³€μˆ˜λ‘œ μ „λ‹¬ν•˜κ±°λ‚˜, μ„€μ • 객체λ₯Ό 톡해 λ™μž‘μ„ μ œμ–΄ν•˜λŠ” 방식을 μ‚¬μš©ν•˜μ„Έμš”. μ΄λŠ” 훨씬 μ•ˆμ „ν•˜κ³  λ””λ²„κΉ…ν•˜κΈ° μ‰¬μš΄ μ ‘κ·Ό λ°©μ‹μž…λ‹ˆλ‹€.

4. μ›Ή μ–΄μ…ˆλΈ”λ¦¬ (WebAssembly) κ³ λ € (κ³ μ„±λŠ₯ 계산 둜직)

맀우 λ³΅μž‘ν•˜κ±°λ‚˜ μ„±λŠ₯이 μ€‘μš”ν•œ 계산 λ‘œμ§μ„ λ™μ μœΌλ‘œ λ‘œλ“œν•˜κ³  μ‹€ν–‰ν•΄μ•Ό ν•œλ‹€λ©΄, μ›Ή μ–΄μ…ˆλΈ”λ¦¬λ₯Ό κ³ λ €ν•  수 μžˆμŠ΅λ‹ˆλ‹€. μ›Ή μ–΄μ…ˆλΈ”λ¦¬λŠ” JavaScriptμ™€λŠ” λ‹€λ₯Έ 이진 ν˜•μ‹μœΌλ‘œ, μƒŒλ“œλ°•μŠ€ ν™˜κ²½μ—μ„œ μ‹€ν–‰λ˜μ–΄ λ³΄μ•ˆμ„±μ΄ λ†’κ³  μ„±λŠ₯도 λ›°μ–΄λ‚©λ‹ˆλ‹€.

핡심은 'μ½”λ“œ μ‹€ν–‰'이 μ•„λ‹Œ '데이터 처리' λ˜λŠ” 'ν•¨μˆ˜ 전달'둜 문제λ₯Ό ν•΄κ²°ν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€. μ΄λŠ” μ½”λ“œμ˜ μ•ˆμ •μ„±, λ³΄μ•ˆμ„±, 그리고 μœ μ§€λ³΄μˆ˜μ„±μ„ 크게 ν–₯μƒμ‹œν‚΅λ‹ˆλ‹€.

Before Code (Bad)

// case 1: JSON λ¬Έμžμ—΄μ„ 객체둜 λ³€ν™˜
const unsafeJsonString = "({ name: 'Alice', age: 30 })"; // μ‹€μ œλ‘œλŠ” μ‚¬μš©μž μž…λ ₯ λ“± μ™ΈλΆ€μ—μ„œ 올 수 있음
const user = eval(unsafeJsonString); // 🚨 λ³΄μ•ˆ 취약점: XSS μœ„ν—˜!
console.log(user.name);

// case 2: λ™μ μœΌλ‘œ ν•¨μˆ˜ 생성
const operation = 'a + b';
const calculate = new Function('a', 'b', `return ${operation};`); // 🚨 λ³΄μ•ˆ 취약점 및 μ„±λŠ₯ μ €ν•˜
console.log(calculate(5, 3));

// case 3: setTimeout/setInterval에 λ¬Έμžμ—΄ 전달 (evalκ³Ό μœ μ‚¬)
const maliciousCode = "alert('μ„Έμ…˜ νƒˆμ·¨λ¨!');";
setTimeout(maliciousCode, 1000); // 🚨 evalκ³Ό λ™μΌν•œ λ³΄μ•ˆ μœ„ν—˜!

After Code (Good)

// case 1: JSON λ¬Έμžμ—΄μ„ 객체둜 λ³€ν™˜
// μ‚¬μš©μž μž…λ ₯이 JSON ν˜•νƒœλ‘œ 올 경우, λ°˜λ“œμ‹œ JSON.parse() μ‚¬μš©
const safeJsonString = '{"name": "Bob", "age": 25}'; 
try {
  const user = JSON.parse(safeJsonString); // ✨ μ•ˆμ „ν•˜κ³  효율적인 방법
  console.log(user.name);
} catch (e) {
  console.error('JSON νŒŒμ‹± μ—λŸ¬:', e);
}

// case 2: λ™μ μœΌλ‘œ ν•¨μˆ˜ 생성 (데이터/ν•¨μˆ˜ 전달 방식)
const operationFunction = (a, b) => a + b; // 미리 μ •μ˜λœ ν•¨μˆ˜ μ‚¬μš©
console.log(operationFunction(5, 3));

// λ˜λŠ” μ„€μ • 객체λ₯Ό 톡해 동적 λ™μž‘ μ œμ–΄
const config = {
  operation: 'add',
  perform: (a, b) => a + b
};
console.log(config.perform(5, 3));

// case 3: setTimeout/setInterval에 ν•¨μˆ˜ 전달
const secureAction = () => {
  console.log('μ•ˆμ „ν•˜κ²Œ μ‹€ν–‰λ˜μ—ˆμŠ΅λ‹ˆλ‹€.');
};
setTimeout(secureAction, 1000); // ✨ ν•¨μˆ˜ μ°Έμ‘°λ₯Ό 직접 전달