跨平台混合框架(Cordova/RN/Flutter/Electron)加固
防御目标与威胁面
跨平台混合框架在复用前端/跨端代码的同时,常因桥接层权限过大、调试产物未剥离或导航白名单过宽导致沙箱失效:
- Cordova / Capacitor:
config.xml配置<access origin="*" />或含通配符的<allow-navigation>、capacitor.config.json未限制allowNavigation,配合cordova-plugin-file/InAppBrowser或未受控的 CSP 即可演变为私有文件外带。 - React Native:生产包开启
__DEV__调试模式、暴露 Metro Bundler(8081 端口)与.mapSourceMap、明文 JS Bundle 硬编码密钥,以及NativeModules/TurboModules 对参数未校验。 - Flutter:意外发布 Debug/Profile 包携带
assets/kernel_blob.bin源码,或MethodChannel未验证调用来源与路径参数,且 Dart 默认网络库绕过系统代理/需显式实施 Pinning。 - Electron:开启
nodeIntegration: true、关闭contextIsolation/sandbox、contextBridge直接暴露ipcRenderer/shell/eval,或自定义协议处理器直接拼接命令引发 RCE。
核心防御矩阵
| 框架 | 核心风险点 | 根治控制措施 |
|---|---|---|
| Cordova / Capacitor | <access origin="*" />、allow-navigation 通配、冗余文件插件、弱 CSP | 精确限定域名白名单(无 * 通配),移除非必要文件/传输插件,强制严格 CSP (script-src 'self') |
| React Native | Metro/SourceMap 泄露、__DEV__ 调试、明文 JS 与无校验 NativeModules | 发布版强制 hermes_enabled=true 编译为 .hbc 字节码并剥离 .map;原生模块校验路径与类型白名单 |
| Flutter | 携带 kernel_blob.bin、Dart 符号未混淆、MethodChannel 参数注入 | 强制 flutter build apk --release --obfuscate --split-debug-info,Native 校验通道参数并实施 SPKI Pinning |
| Electron | nodeIntegration、无隔离 preload、原型链污染、协议处理器命令注入 | 强制 nodeIntegration:false + contextIsolation:true + sandbox:true;contextBridge 仅暴露窄接口且校验 senderFrame.url |
加固代码与跨框架发布基线校验示例
1. Electron 安全 BrowserWindow 与最小权限 preload.js 模板
javascript
// main.js (Electron 安全窗口基线)
const win = new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, "preload.js"),
nodeIntegration: false,
contextIsolation: true,
sandbox: true,
webSecurity: true,
allowRunningInsecureContent: false
}
});
// preload.js (仅暴露固定参数校验后的单一 IPC 通道,禁止透传 ipcRenderer/shell)
const { contextBridge, ipcRenderer } = require("electron");
contextBridge.exposeInMainWorld("appBridge", {
readProfile: (userId) => {
if (typeof userId !== "string" || !/^[a-zA-Z0-9_-]{1,32}$/.test(userId)) {
throw new Error("invalid_user_id");
}
return ipcRenderer.invoke("profile:read", userId);
}
});2. Python 跨平台安装包发布期硬性检查器
python
import io
import json
import xml.etree.ElementTree as ET
import zipfile
from typing import Dict, List
def verify_hybrid_release_apk(apk_bytes: bytes) -> List[str]:
"""检查混合框架 APK 是否残留调试产物、SourceMap 或宽松导航白名单。"""
findings: List[str] = []
with zipfile.ZipFile(io.BytesIO(apk_bytes), "r") as zf:
names = zf.namelist()
# 1. Flutter Debug/Profile 产物拦截
if "assets/kernel_blob.bin" in names:
findings.append("flutter_debug_kernel_blob_present")
# 2. React Native / 前端 SourceMap 残留拦截
sourcemaps = [n for n in names if n.endswith(".map")]
if sourcemaps:
findings.append(f"sourcemap_exposed:{sourcemaps}")
# 3. React Native 未编译为 Hermes 字节码(仍为明文 JS Bundle 且含 __DEV__=true)
if "assets/index.android.bundle" in names:
bundle = zf.read("assets/index.android.bundle")
if not bundle.startswith(b"\xc6\x1f\xbc\x03"): # Hermes magic check
if b"__DEV__=true" in bundle or b"__DEV__ = true" in bundle:
findings.append("rn_dev_mode_enabled_in_bundle")
# 4. Cordova config.xml 宽松访问策略检查
for cfg_name in ("res/xml/config.xml", "assets/www/config.xml"):
if cfg_name in names:
root = ET.fromstring(zf.read(cfg_name))
for acc in root.findall(".//access"):
if acc.attrib.get("origin") == "*":
findings.append(f"cordova_wildcard_access:{cfg_name}")
for nav in root.findall(".//allow-navigation"):
href = nav.attrib.get("href", "")
if "*" in href:
findings.append(f"cordova_wildcard_navigation:{href}")
# 5. Capacitor 配置检查
if "assets/capacitor.config.json" in names:
cap = json.loads(zf.read("assets/capacitor.config.json"))
nav_list = cap.get("server", {}).get("allowNavigation", [])
if "*" in nav_list or any("*" == x for x in nav_list):
findings.append("capacitor_wildcard_navigation")
return findings
if __name__ == "__main__":
buf = io.BytesIO()
with zipfile.ZipFile(buf, "w") as zf:
zf.writestr("assets/kernel_blob.bin", b"dart-kernel")
zf.writestr("assets/index.android.bundle.map", b"{}")
zf.writestr("assets/index.android.bundle", b"var __DEV__ = true;")
zf.writestr("res/xml/config.xml", "<widget><access origin='*'/><allow-navigation href='https://*.example.com/*'/></widget>")
zf.writestr("assets/capacitor.config.json", json.dumps({"server": {"allowNavigation": ["*"]}}))
issues = verify_hybrid_release_apk(buf.getvalue())
assert "flutter_debug_kernel_blob_present" in issues
assert any(i.startswith("sourcemap_exposed:") for i in issues)
assert "rn_dev_mode_enabled_in_bundle" in issues
assert any(i.startswith("cordova_wildcard_access:") for i in issues)
assert any(i.startswith("cordova_wildcard_navigation:") for i in issues)
assert "capacitor_wildcard_navigation" in issues
clean_buf = io.BytesIO()
with zipfile.ZipFile(clean_buf, "w") as zf:
zf.writestr("assets/index.android.bundle", b"\xc6\x1f\xbc\x03\x00\x00")
zf.writestr("res/xml/config.xml", "<widget><access origin='https://api.example.com'/></widget>")
zf.writestr("assets/capacitor.config.json", json.dumps({"server": {"allowNavigation": ["app.example.com"]}}))
assert verify_hybrid_release_apk(clean_buf.getvalue()) == []
print("Hybrid framework release check passed:", issues)防御工作流
text
1. 构建产物规范:统一通过 Release 流水线出包,禁止 Debug/Profile 产物与 .map SourceMap 进入安装包
2. 导航与源站收敛:收紧 Cordova config.xml、Capacitor allowNavigation 及 Electron 加载白名单,消除通配域
3. 桥接最小权限化:逐个审查 NativeModules、MethodChannel 与 Electron contextBridge,拒绝文件路径/命令直通参数
4. 字节码与混淆开启:React Native 启用 Hermes 字节码编译,Flutter 开启 --obfuscate 与 --split-debug-info
5. 协议与网络一致性:Electron 协议处理器严禁拼接 Shell 命令,跨端网络层统一落地 TLS Pinning验证与判定标准
- CI 产物拦截:对发布 APK 运行
verify_hybrid_release_apk必须返回空列表,不得存在kernel_blob.bin、*.map、__DEV__=true或*导航通配符。 - 桥接越权阻断:向
NativeModules/MethodChannel/appBridge传入目录遍历路径(../)或非预期参数类型时,原生处理层必须在调用底层 API 前拒绝执行。 - Electron 沙箱验证:在渲染进程 DevTools 中尝试访问
require、process或ipcRenderer全量对象必须返回undefined,且外部点击自定义协议链接无法触发命令行执行。
MCP 工具映射
| 防御步骤 | MCP 工具 | 说明 |
|---|---|---|
| 检查包内资源与组件基线 | android_app_baseline / android_package_info | 核实发布包结构、签名与运行态信息 |
| 核查混合框架网络 Pinning | android_http_observation_recipe | 验证跨平台网络栈未绕过系统 Pinning 与 HTTPS 策略 |
| 检查 Flutter/RN 原生库符号 | ghidra_headless_analyze | 排查 libapp.so/NativeModules 是否暴露危险调试接口 |
| 检索对应红蓝文档 | kb_router | 对照 02-cordova-reactnative.md 覆盖全部框架风险点 |