混合开发中sql注入风险集中在原生层数据库调用与webview桥接接口,须禁用动态sql执行、强制参数化查询、校验输入源头并保护sqlite文件权限。

混合开发(Hybrid App)中,SQL注入漏洞常被误判为“只存在于Web端”,但实际风险集中在原生层调用数据库(如 SQLite)、WebView 中执行 eval() 或拼接 JS 字符串触发本地 SQL 查询的环节。单纯加固 H5 页面的 fetch 请求或后端 API 没用——原生桥接接口才是重灾区。
WebView 里直接执行 SQL 的场景必须禁用
很多老项目在 Android/iOS 原生层暴露了类似 executeSql、runQuery 这样的 JSBridge 方法,前端通过 window.webviewBridge.executeSql("SELECT * FROM user WHERE id = " + id) 调用。这和 PHP 里写 "WHERE id = " . $_GET['id'] 一样危险。
- 立即检查所有 JSBridge 注册的函数名,搜索关键词:
sql、query、exec、raw - 若存在,必须废弃该接口,改用预定义动作(如
getUserById),由原生层内部用参数化方式查询 SQLite - 禁止 WebView 中使用
eval()、Function()、setTimeout(string)等可执行动态字符串的 API
Android SQLite 使用 rawQuery 时必须用 ? 占位符
Android 原生代码里调用 SQLiteDatabase.rawQuery() 是高危操作,但很多人以为“没走网络就安全”,其实攻击者可通过本地存储、Intent 参数、甚至 URL Scheme 注入恶意字符串。
- 错误写法:
db.rawQuery("SELECT * FROM logs WHERE type = '" + type + "'", null) - 正确写法:
db.rawQuery("SELECT * FROM logs WHERE type = ?", new String[]{type}) - 注意:
selectionArgs参数必须是非空数组;若需动态表名或列名(极少见),只能白名单校验,不能参数化 - SQLite 不支持命名参数(如
:type),只认?,别套用 PostgreSQL 风格
iOS WKWebView 与原生交互中的字符串拼接陷阱
iOS 项目常用 WKScriptMessageHandler 接收 JS 发来的指令,再转成本地 SQLite 查询。如果 JS 传的是完整 SQL 字符串(如 {"sql": "DELETE FROM cache WHERE key LIKE '%"+key+"%'"} ),那 Objective-C/Swift 层不做解析直接执行,等于开门揖盗。
- JS 端只能传结构化数据:
{action: "deleteCache", keyPrefix: "user_"},严禁传sql字段 - 原生层收到后,用白名单判断
action是否合法,再拼接固定语句:DELETE FROM cache WHERE key LIKE ? || '%' - Swift 示例:
db.execute("DELETE FROM cache WHERE key LIKE ? || '%'", arguments: [keyPrefix])(使用GRDB或SQLite.swift等支持参数化的库) - 别信“用户没法改本地 JS”——调试模式、越狱设备、Frida Hook 都能篡改 JSBridge 调用
混合应用的权限与数据库文件保护常被忽略
即使 SQL 查询本身安全,SQLite 文件若被导出或任意读写,攻击者可绕过所有逻辑直接拖库。尤其 Android 的 MODE_WORLD_READABLE 已废弃,但仍有项目误用 getDatabasePath() 返回可被其他 App 访问的路径。
- Android:确保数据库创建时用
Context.MODE_PRIVATE,且不调用setReadable(true)或setWritable(true) - iOS:SQLite 文件必须存于
Library/Application Support/或Documents/,且启用NSFileProtectionComplete(App 在后台时加密) - WebView 本地存储(
localStorage、WebSQL)已淘汰,禁用WebSQL支持(iOS 16+ 默认关闭,但旧版本需显式禁用)
混合开发里最麻烦的不是“怎么写安全 SQL”,而是“谁在哪个环节控制输入源头”。WebView 的 JS、原生桥接层、SQLite 封装库、甚至 Cordova 插件都可能各自为政地处理参数。一个环节漏掉白名单或硬编码拼接,整条链路就崩了。别假设“前端传来的一定可信”,所有桥接入口都得当外部攻击面来设计。











