학생들이 MongoDB 쿼리를 작성할 수 있는 한 학습 플랫폼이 Node.js의 vm 모듈을 제거하고, 이제 모든 쿼리를 실행하기 전에 추상 구문 트리(AST)로 파싱합니다. 이 변경 사항은 깨질 수 있는 샌드박스를 제거하여, 잘못된 문자열이 주입할 수 있는 임의의 코드로부터 백엔드를 보호합니다.
기존 샌드박스가 실패한 이유
첫 번째 구현 방식은 라이브 데이터베이스 핸들을 vm.runInContext 호출로 감싸고 정규 표현식으로 사용자를 제한하려 했습니다. find나 aggregate와 같은 메서드 이름만 허용하고, 그 외의 모든 식별자는 필터링되도록 설계되었습니다.
두 가지 결함이 해당 방식의 보안을 취약하게 만들었습니다:
- 정규 표현식 필터링은 우회 가능합니다. JavaScript는 코드에서 대괄호 표기법(
obj["constructor"])을 사용하여 모든 속성에 접근할 수 있게 합니다. 공격자는Function생성자를 가져와 새로운 함수를 만들고 원하는 코드를 실행할 수 있습니다. 소스 코드가 수많은 방식으로 재작성될 수 있기 때문에 정규 표현식은 근본적인 속성 접근을 감지할 수 없습니다. vm은 보안 경계가 아닙니다. Node의 문서에 따르면vm은 전역(global) 객체는 격리하지만 전체 프로세스를 격리하지는 않습니다. 샌드박스에 라이브 데이터베이스 연결을 주입함으로써, 컨텍스트 내부의 코드는 데이터를 쓰거나 삭제하는 메서드를 포함하여 해당 연결의 모든 메서드를 호출할 수 있는 능력을 유지하게 됩니다. 샌드박스는 코드가 호스트 프로세스에 영향을 미치는 것을 막지 못했습니다.
AST 기반 솔루션
팀은 코드 실행을 정적 분석으로 교체했습니다. 이제 쿼리 문자열은 acorn 파서로 전달되어 코드의 구문 구조를 트리 형태로 나타내는 AST를 생성합니다. AST는 엄격한 화이트리스트를 기준으로 노드별로 검사됩니다:
- 리터럴, 배열 및 객체는 단순한 값으로 나타날 때만 허용됩니다.
- 메서드 호출은 미리 정의된 세트(
find,sort,limit등)로 제한됩니다. 그 외의 호출은 모두 거부됩니다. - 계산된 속성 접근(예:
obj[expr]) 또는 명시적으로 나열되지 않은 노드 유형은 즉시 실패를 유발합니다.
파서는 원시 텍스트가 아닌 트리를 기반으로 작동하기 때문에, 다른 표기법이나 대괄호 표기법 트릭에 속지 않습니다. 정규 표현식을 통과했을 법한 생성자 체인은 인식되지 않는 노드로 나타나며, 코드가 실행되기 전에 거부됩니다.
보안 측면에서의 의미
새로운 설계는 "기본 차단(deny by default)" 철학을 따릅니다:
- 금지할 것이 아니라 허용할 것을 정의하십시오. 텍스트 기반의 허용 목록(allow-listing)은 모든 경우를 다룰 수 없지만, AST는 노드 유형이 유한하므로 철저한 검사가 가능합니다.
- 샌드박스 내부에 라이브 리소스를 절대 노출하지 마십시오. 격리된 컨텍스트에 데이터베이스 핸들을 전달하면 샌드박스 코드가 백엔드로 직접 연결되는 통로를 갖게 됩니다. 파서 방식은 사용자 코드에 라이브 객체를 절대 전달하지 않으며, 오직 쿼리의 의도만을 추출합니다.
- 구조를 검증한 다음 안전하게 실행하십시오. AST가 검증을 통과하면, 플랫폼은 자체적인 신뢰할 수 있는 코드 경로를 사용하여 허용된 호출을 실제 MongoDB 드라이버 메서드로 변환합니다.
향후 주의할 점
- 코드베이스 내의
eval,new Function또는vm사용을 감사하십시오. 화이트리스트조차 JavaScript의 동적인 특성에 의해 무력화될 수 있습니다. - 가능한 한 사용자 생성 코드에 AST 파싱을 도입하십시오.
acorn,esprima또는babel-parser와 같은 라이브러리를 사용하면 변환이 간단해집니다. - 라이브 객체의 노출을 제한하십시오. 데이터베이스 연결, 파일 핸들 또는 네트워크 소켓에 접근해야 하는 경우, 허용하려는 최소한의 메서드만 노출하는 프록시로 감싸십시오.
- 엣지 케이스(edge cases) 테스트를 자동화하십시오. 대괄호 표기법, 계산된 키 또는 프로토타입 조작을 사용하는 쿼리를 생성하여 파서가 이를 거부하는지 확인하십시오.
요약
vm.runInContext를 샌드박스로 신뢰하는 것은 잘못된 보안 의식을 심어줍니다. 정규 표현식 필터는 JavaScript의 유연한 구문을 모두 커버할 수 없으며, 샌드박스는 프로세스 리소스를 격리하지 못합니다. 사용자 입력을 AST로 파싱하고 이해할 수 있는 노드만 화이트리스트에 포함하는 방식은 악성 코드가 실행되기 전에 차단하는 구체적이고 유지 관리 가능한 장벽을 제공합니다. 플랫폼에서 사용자가 코드를 작성할 수 있게 한다면, 지금 바로 eval 방식의 실행을 정적 분석으로 교체하십시오.
