알려진 CVE & 보안 체크리스트
이 문서가 답하는 질문: “Electron 앱은 실제로 어떻게 뚫렸고, 무엇을 점검하면 그 사고들을 막을 수 있는가?” 한 줄 답 (Pyramid Top): “공식 Security Checklist 13항목은 지난 10년의 실제 CVE를 일반화한 결과다 — 하나라도 빠지면 어떤 사고가 일어났는지 역사가 보여준다.”
Why — 왜 존재하는가
Electron 앱의 공격면은 웹 + Node + OS의 합집합이다. 사고가 일어나면 사용자 디스크 전체가 위험에 노출된다. 공식 Security Checklist는 13개 항목으로 구성되며 — 각 항목 뒤에는 그것을 어겨서 일어났던 실제 사건이 있다.
| 사고의 출처 | 빈도 | 대응 항목 |
|---|---|---|
| nodeIntegration이 켜진 채 외부 콘텐츠 로드 | 가장 많음 | #2, #3 |
| 입력 검증 없는 IPC | 빠르게 증가 | #5 |
| 외부 URL 검증 없는 shell.openExternal | 자주 | #10 |
| 미사인 빌드 / 가짜 업데이트 | 중간 | (배포 챕터) |
How — 어떻게 점검하는가
What — 13항목 + 실제 CVE
13항목 체크리스트 (공식)
| # | 항목 | 위반 시 사고 |
|---|---|---|
| 1 | 신뢰 가능한 콘텐츠만 로드 | http://로 외부 콘텐츠 로드 → MITM 통한 RCE |
| 2 | Renderer에서 nodeIntegration: false | Discord RCE (2018) |
| 3 | contextIsolation: true | Slack 2020 — preload 변수 누설 |
| 4 | sandbox: true 활성화 | Node API 잔존 통로 |
| 5 | session permission 핸들러 등록 | 임의 origin이 카메라 사용 |
| 6 | webSecurity 끄지 말 것 | CORS 무력화 → 데이터 유출 |
| 7 | CSP 정의 | XSS 1차 방어선 누락 |
| 8 | allowRunningInsecureContent: false 유지 | mixed content 통한 인젝션 |
| 9 | 실험 기능(experimentalFeatures) 비활성 | 미검증 API 노출 |
| 10 | <webview>의 enableBlinkFeatures 검사 | 보안 무력화된 Blink 기능 |
| 11 | will-navigate로 네비게이션 검증 | 외부 URL 우회 로드 |
| 12 | setWindowOpenHandler로 새 창 검사 | popup → 새 BrowserWindow 권한 상승 |
| 13 | shell.openExternal은 사용자 입력 검증 | URL 인젝션 → 시스템 명령 실행 |
실제 CVE 케이스 분석
CVE-2018-15685 — Discord (RCE via window.open)
- 원인: nodeIntegration: true 상태에서
window.open()이 nodeIntegration을 상속. - 공격: XSS 발견 →
window.open→ 새 창에서require('child_process').exec(). - 대응: nodeIntegration 디폴트 false (Electron 5), nativeWindowOpen 분리, setWindowOpenHandler로 webPreferences 강제.
CVE-2018-1000136 — Atom Editor (Markdown preview RCE)
- 원인: MathML 파서 XSS + nodeIntegration: true.
- 공격:
.md파일 미리보기 →<math>태그로 JS 실행 → require(‘fs’)로 임의 파일 읽기. - 대응: 마크다운 렌더링은 별도 BrowserWindow에서 nodeIntegration: false로 격리.
CVE-2020-15174 — Electron Builder (file:// path traversal)
- 원인: file:// origin이 없음(null) → SOP 우회.
- 공격: 로컬 HTML에서 다른 디렉토리 파일 fetch.
- 대응: 커스텀 protocol(
app://) 사용 —file://직접 사용 금지.
CVE-2018-1000006 — Electron Protocol Handler (Windows)
- 원인: Windows 커스텀 protocol에서 인자가 쉘 인자처럼 처리됨.
- 공격:
myapp://...--gpu-launcher="cmd /c calc"→--gpu-launcher플래그 인젝션 → 임의 명령 실행. - 대응: app.setAsDefaultProtocolClient 사용 시 인자 검증, Chromium의
--gpu-launcher같은 위험 플래그 차단.
CVE-2019-1020009 — WhatsApp Desktop (Open Redirect → RCE)
- 원인: 메시지의 preview 링크 검증 미흡 + nodeIntegration.
- 공격: 악성 링크 클릭 → 정보 누출 + 일부 환경에서 RCE.
- 대응: 외부 링크는
shell.openExternal+ URL allowlist.
자동 점검 — Electronegativity
# 정적 분석 도구
npm install -g @doyensec/electronegativity
electronegativity -i ./my-electron-app -o report.csv| 점검 항목 | 예시 |
|---|---|
| AUXCLICK_JS_CHECK | auxclick 이벤트 미가드 |
| CONTEXT_ISOLATION_JS_CHECK | contextIsolation 비활성 |
| DANGEROUS_FUNCTIONS_JS_CHECK | eval, exec 호출 |
| HTTP_RESOURCES_JS_CHECK | http:// 자원 로드 |
| INSECURE_CONTENT_HTML_CHECK | mixed content |
| NODE_INTEGRATION_JS_CHECK | nodeIntegration: true |
| … | 30+ 룰 |
Electron Fuses — 빌드 타임 보안 고정
// forge.config.js
const { FuseV1Options, FuseVersion } = require('@electron/fuses');
module.exports = {
plugins: [
{ name: '@electron-forge/plugin-fuses',
config: {
version: FuseVersion.V1,
[FuseV1Options.RunAsNode]: false, // ELECTRON_RUN_AS_NODE 차단
[FuseV1Options.EnableCookieEncryption]: true,
[FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false,
[FuseV1Options.EnableNodeCliInspectArguments]: false,
[FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true,
[FuseV1Options.OnlyLoadAppFromAsar]: true,
}
}
]
};이들은 실행 파일 바이트에 새겨진다. 사용자가 바이너리를 패치해도 — 그 바이트가 바뀌면 Electron이 거부.
안전한 webPreferences 한 묶음
new BrowserWindow({
webPreferences: {
contextIsolation: true,
sandbox: true,
nodeIntegration: false,
nodeIntegrationInWorker: false,
nodeIntegrationInSubFrames: false,
webSecurity: true,
allowRunningInsecureContent: false,
experimentalFeatures: false,
enableBlinkFeatures: '', // 빈 문자열
preload: path.join(__dirname, 'preload.js'),
}
});What-if — 잘못 쓰면
- 함정 1: 체크리스트의 일부만 적용 → “보안 했어요” 착각. CVE의 90%는 한 항목만 위반해서 일어났다.
- 함정 2: Electronegativity가 false positive가 많아 무시 → 진짜 위반도 같이 묻힌다. 점진적으로 noise 줄이며 활용.
- 함정 3: Fuses 적용 안 함 → 사용자가
ELECTRON_RUN_AS_NODE환경변수로 Node 인터프리터처럼 앱 바이너리 실행 → 임의 코드. - 함정 4: 외부 보안 감사 없음 → 자기 코드를 자기가 검토 → 같은 함정을 못 본다. 1년에 한 번 외부 audit 권장.
Insight — 흥미로운 이야기
“Electron 보안의 역사는 디폴트의 역사다”
Electron 5(2019): nodeIntegration 디폴트 false. 그 전엔 모든 앱이 시한폭탄이었다. Electron 12(2021): contextIsolation 디폴트 true. preload 변수 누설 시대 종료. Electron 22(2023): sandbox 디폴트 true. Renderer가 Chromium sandbox로 격리. Electron 28(2023):
remote모듈 완전 제거. 원격 객체 노출 패턴 영구 폐지.흥미로운 건 — Electron 팀이 디폴트를 바꿀 때마다 기존 앱의 일부가 깨졌다. 그때마다 커뮤니티에서 “왜 이런 변경을?”이라는 반발이 있었지만, 시간이 지나 보면 그 변경 없이는 다음 사고가 막을 수 없었다. 보안 디폴트는 불편을 통해 사고를 막는 거래다 — 이 13항목 체크리스트는 그 거래의 합의된 가격표다.
요약
- 13항목은 역사의 일반화 — 빠뜨릴 자유가 없다.
- Electronegativity로 자동 1차 점검, 수동 audit으로 보강.
- Fuses는 런타임 가드를 빌드 타임으로 고정하는 마지막 layer.
- 사고는 한 항목 위반에서 시작한다 — 전체 통과가 baseline.