Javascript 코드를 서버에서 안전하게 실행시키는 방법 (feat. isolated-vm) | Web IDE 구축하기 - 1
사용자에게 받은 코드를 서버단에서 어떻게 실행시켜야 할까?
제가 이번에 개발하려는 Web IDE는 React + Node.js 환경으로 구축하려고 합니다
가장 먼저 Javascript 환경인 만큼, Javascript 코드를 클라이언트에게 받아서 서버에 어떻게 실행시켜야 할까요?
eval() 함수 또는 new Function이 가장 먼저 떠오를 수 있을 겁니다
하지만 이 함수들은 절대로 사용합니다 보안상의 큰 문제를 야기시키기 때문입니다
그럼 어떻게 안전한 환경에서 코드를 실행시킬 수 있을까요?
개인적으론 그다음에 떠오른 생각이 좋~은 인터프리터가 있지 않을까? 였는데요ㅋㅎ...
당연 없습니다... 허허
일단 가장 먼저 가상환경(가상머신)에서 코드를 실행시켜야 합니다
그리고 안전성이 보장된 샌드박스 환경을 만들어줘야 합니다
샌드박스 환경
통제된 환경 내에서 프로그램을 동작시키는 것
소프트웨어 보안 기법에 속하는 샌드박스 환경을 Nodejs에서 제공해주고 있습니다
바로 VM 모듈입니다
이 외에도 VM2도 제공하고, 최근 isolated-vm도 생겨났습니다
- VM
- Node.js 애플리케이션 내부에 가상머신 컨텍스트로 스크립트를 실행하는 환경을 제공해 줍니다
- 샌드박스 환경으로 코드를 실행시켜 실제 호스트 시스템과 메인 스크립트에는 접근하는 위험을 선제적으로 방어할 수 있습니다
- VM은 별도의 npm 설치 없이 바로 사용이 가능합니다
- 그러나 메인 프로세스에 접근이 아예 불가능한 건 아니고 접근하는 방법이 있긴 합니다
- VM2
- 코어 모듈인 VM에 보안성을 추가한 모듈입니다
- 별도로 npm을 통한 설치를 진행해야 합니다
- 그러나 최근에 심각한 보안 취약점이 발견되면서 개발 중단되었습니다 해당 개발자도 더 이상 사용하지 말라고 하고 있습니다
- isolated-vm
- VM2의 보안 취약점 발견 후 마이그레이션 하라며 생겨난 VM입니다
- 주요 기능만 따로 분리한 것 같으며, 적극적인 업데이트는 예정된 바가 없다고는 하네요
- 별도로 npm을 통한 설치와 설치 전제 조건으로 컴파일러가 설치되어있어야 합니다
- 개발자는 해당 모듈에 전적으로 신뢰하기보다는 다양한 보안 기법을 고려해야 한다고 말하네요 허허
isolated-vm 빠르게 시작하기
npm install isolated-vm
이미 공식문서에 예제가 잘 써져 있지만 간단하게 적어봅니다
import ivm from "isolated-vm";
const code = `(function() { return 'Hello, Isolate!'; })()`;
const isolate = new ivm.Isolate({ memoryLimit: 8 }); // 메모리를 8MB 제한
const script = isolate.compileScriptSync(code); // 컴파일 할 자바스크립트 코드
const context = isolate.createContextSync(); // 컨텍스트 생성, 다른 컨텍스트에는 영향을 끼치지 않음
console.log(script.runSync(context)); // Hello, Isolate!
핵심 API와 결괏값을 반환해 주는 형태는 간단하게 위와 같습니다
한 가지 특이한 점은 console.log 또는 log, setTimeout ...등등 과 같은 메서드는 읽히지가 않는 것인데요
해당 메서드가 포함된 코드를 받아서 실행시키면 서버 쪽에도 찍히는게 없거나(무반응), 에러를 반환합니다
(제가 서버쪽 지식은 잘 몰라서 특이하다고 생각하는 걸 지도)
(일단, setTimeout은 우리가 브라우저에서 쓰는 명세랑 Nodejs에서 쓰는 명세가 다른 것 같습니다)
console.log, log 등등을 읽고 싶다면 해당 코드가 실행되는 context안 global 전역 객체에 추가를 해줘야 합니다
const returnCallback = function (...args) {
console.log(...args);
};
context.evalClosureSync(
`global.console.log = function(...args) {
$0.applyIgnored(undefined, args, { arguments: { copy: true } });
}`,
[returnCallback],
{ arguments: { reference: true } }
);
위와 같이 코드를 작성해 주면 global안에 console이 들어가고 그다음 log가 들어갑니다
클라이언트가 console.log("블라블라") 라고 쳐서 서버에 전송을 시키면
evalClosureSync API를 통해 console.log가 무엇인지를 읽고 실행시켜 (컨텍스트 안에서 코드를 컴파일하고 실행)
전달받은 인수 ("블라블라")를 통해 괄호 안 내용을 args에 복사합니다
그리고 그다음 콜백 함수 returnCallback가 실행됩니다
returnCallback 함수에서 전달받은 args에는 "블라블라"가 담겨있고,
드디어 서버단에서 "블라블라" 라는게 찍히게 됩니다
이런 식으로 코드를 읽고 실행시킬 수 있습니다
마무리
아직까지는 Nodejs 기본적인 환경 구축과 isolated-vm에 대한 기초적인 사용법만 익힌 상태입니다
서버 쪽 지식이 풍부하지 않은 상태에서 꽤나 난이도 있는 기능을 만드려니 개발 속도에 더딤이 큰 상태입니다
이 쪽 분야는 단순 웹 API 만들기보다는 V8엔진, 컴파일 지식, 보안 지식도 꽤나 갖춰야 할 것 같더라고요
그래도 모르던 세계에(?) 입문한 것 같아 즐겁습니다
앞으로 종종 해당 프로젝트를 개발하면서 부딪혔던 이슈와 유익하다 싶은 개념들을 정리하려고 합니다