프로세스 샌드박싱
Chromium의 주요 보안 기능 중 하나는 프로세스를 샌드박스 내에서 실행할 수 있다는 점이다. 샌드박스는 악성 코드가 시스템 리소스에 접근하는 것을 제한함으로써 피해를 최소화한다. 샌드박스 내부의 프로세스는 CPU 사이클과 메모리만 자유롭게 사용할 수 있다. 추가 권한이 필요한 작업을 수행하려면, 샌드박스 프로세스는 전용 통신 채널을 통해 더 높은 권한을 가진 프로세스에 작업을 위임한다.
Chromium에서는 메인 프로세스를 제외한 대부분의 프로세스에 샌드박싱을 적용한다. 여기에는 렌더러 프로세스뿐만 아니라 오디오 서비스, GPU 서비스, 네트워크 서비스와 같은 유틸리티 프로세스도 포함된다.
자세한 내용은 Chromium의 Sandbox design document를 참고한다.
Electron 20부터는 별도의 설정 없이 렌더러 프로세스에 샌드박스가 활성화된다. 특정 프로세스에 대해 샌드박스를 비활성화하려면 싱글 프로세스에 대한 샌드박스 비활성화 섹션을 참고한다.
Electron의 샌드박스 동작 방식
Electron의 샌드박스 프로세스는 대부분 Chromium과 동일하게 동작한다. 하지만 Electron은 Node.js와 상호작용하기 때문에 몇 가지 추가적인 개념을 고려해야 한다.
렌더러 프로세스
Electron에서 렌더러 프로세스가 샌드박스화되면, 일반적인 Chrome 렌더러와 동일하게 동작한다. 샌드박스화된 렌더러는 Node.js 환경이 초기화되지 않는다.
따라서 샌드박스가 활성화되면, 렌더러 프로세스는 파일 시스템과 상호작용하거나 시스템 변경, 하위 프로세스 생성과 같은 권한이 필요한 작업을 수행할 때, 프로세스 간 통신(IPC)을 통해 메인 프로세스에 위임해야 한다.
프로세스 간 통신에 대한 자세한 내용은 IPC 가이드를 참고한다.
미리 실행되는 스크립트(Preload scripts)
렌더러 프로세스가 메인 프로세스와 통신할 수 있도록 하기 위해, 샌드박스된 렌더러에 연결된 미리 실행되는 스크립트는 여전히 Node.js API의 일부를 사용할 수 있다. Node의 require
모듈과 유사한 require
함수가 제공되지만, 이 함수는 Electron과 Node의 내장 모듈 중 일부만 가져올 수 있다:
electron
(다음 렌더러 프로세스 모듈:contextBridge
,crashReporter
,ipcRenderer
,nativeImage
,webFrame
,webUtils
)events
timers
url
node: imports도 지원된다:
또한, 미리 실행되는 스크립트는 특정 Node.js 기본 기능을 전역 변수로 제공한다:
require
함수는 기능이 제한된 폴리필이기 때문에, CommonJS 모듈을 사용해 미리 실행되는 스크립트를 여러 파일로 나눌 수 없다. 미리 실행되는 코드를 분할해야 한다면, webpack이나 Parcel 같은 번들러를 사용해야 한다.
미리 실행되는 스크립트에 제공되는 환경은 샌드박스된 렌더러보다 훨씬 더 많은 권한을 가지고 있기 때문에, contextIsolation
이 활성화되지 않으면 렌더러 프로세스에서 실행되는 신뢰할 수 없는 코드에 권한 있는 API가 노출될 수 있다.
샌드박스 설정
대부분의 앱에서 샌드박스를 사용하는 것이 최선의 선택이다. 하지만 특정 사용 사례에서는 샌드박스와 호환되지 않는 경우가 있다. 예를 들어, 렌더러에서 네이티브 노드 모듈을 사용할 때가 그렇다. 이런 경우 특정 프로세스에 대해 샌드박스를 비활성화할 수 있다. 하지만 이는 보안 위험을 초래할 수 있다. 특히, 샌드박스가 적용되지 않은 프로세스에 신뢰할 수 없는 코드나 콘텐츠가 존재할 경우 더욱 그렇다.
싱글 프로세스에 대한 샌드박스 비활성화
Electron에서는 BrowserWindow
생성자에 sandbox: false
옵션을 설정해 특정 프로세스에 대해 렌더러 샌드박싱을 비활성화할 수 있다.
app.whenReady().then(() => {
const win = new BrowserWindow({
webPreferences: {
sandbox: false
}
})
win.loadURL('https://google.com')
})
렌더러에서 Node.js 통합이 활성화된 경우에도 샌드박싱이 비활성화된다. 이는 nodeIntegration: true
플래그를 통해 BrowserWindow 생성자에서 설정할 수 있다.
app.whenReady().then(() => {
const win = new BrowserWindow({
webPreferences: {
nodeIntegration: true
}
})
win.loadURL('https://google.com')
})
모든 렌더러에 샌드박스 강제 적용
모든 렌더러에 샌드박스를 강제로 적용하려면 app.enableSandbox
API를 사용할 수 있다. 이 API는 앱의 ready
이벤트가 발생하기 전에 호출해야 한다.
app.enableSandbox()
app.whenReady().then(() => {
// `app.enableSandbox()`가 호출되었으므로 `sandbox:false` 설정은 무시된다.
const win = new BrowserWindow()
win.loadURL('https://google.com')
})
Chromium 샌드박스 비활성화하기 (테스트 목적으로만)
--no-sandbox
커맨드라인 플래그를 사용하면 Chromium의 샌드박스를 완전히 비활성화할 수 있다. 이 플래그는 모든 프로세스(유틸리티 프로세스 포함)에 대해 샌드박스를 비활성화한다. 이 플래그는 테스트 목적으로만 사용하고, 프로덕션 환경에서는 절대 사용하지 않는 것을 강력히 권장한다.
참고로 sandbox: true
옵션은 여전히 렌더러의 Node.js 환경을 비활성화한다.
신뢰할 수 없는 콘텐츠 렌더링에 대한 주의 사항
Electron에서 신뢰할 수 없는 콘텐츠를 렌더링하는 것은 아직 완전히 탐구되지 않은 분야다. 일부 앱(예: Beaker Browser)은 성공적으로 이를 구현하고 있지만, 여전히 몇 가지 근본적인 문제로 인해 Chrome의 보안 수준에는 미치지 못한다.
- Chromium은 제품 보안을 위해 전담 리소스와 전문성을 보유하고 있지만, Electron은 그렇지 않다. 우리는 Chromium에서 가능한 모든 것을 상속하고, 보안 문제에 신속히 대응하기 위해 최선을 다하지만, Chromium만큼의 리소스를 투자할 수 없기 때문에 동일한 수준의 보안을 제공할 수 없다.
- Chrome의 일부 보안 기능(예: Safe Browsing, Certificate Transparency)은 중앙 집중식 권한과 전용 서버가 필요하다. 이는 Electron 프로젝트의 목표와 상충되므로, 이러한 기능을 비활성화한다. 이로 인해 해당 기능이 제공할 수 있는 보안 이점을 포기해야 한다.
- Chromium은 하나뿐이지만, Electron 기반으로 개발된 수천 개의 앱은 각각 약간씩 다르게 동작한다. 이러한 차이를 고려하면 가능성이 무궁무진해지고, 특이한 사용 사례에서 플랫폼의 보안을 보장하기가 어려워진다.
- 보안 업데이트를 사용자에게 직접 푸시할 수 없기 때문에, 앱 공급자가 앱의 Electron 버전을 업그레이드해야 사용자에게 보안 업데이트가 전달된다.
우리는 Chromium의 보안 수정 사항을 이전 버전의 Electron에 백포트하기 위해 최선을 다하지만, 모든 수정 사항이 백포트될 것이라고 보장하지는 않는다. 보안을 유지하기 위한 최선의 방법은 Electron의 최신 안정 버전을 사용하는 것이다.