必须用?占位符+元组传参,禁用execute_script();sqlite仅支持?不支持:name;用户输入须全走参数化;单值参数必须写成(value,);字段名/表名等结构元素需白名单校验。

必须用 ? 占位符 + 元组传参,禁用 execute_script(),其他技巧都是补救,不是替代。
只用 execute() 和 executemany() 配合 ? 占位符
SQLite 不支持命名参数(如 :name 或 @param),只认 ?。所有用户可控输入——不管是来自 MQTT、HTTP 请求、传感器上报,还是 OTA 配置——都必须走这个路径。
- 参数必须是元组:哪怕只有一个值,也得写成
(value,),写成[value]或value会触发sqlite3.ProgrammingError: Incorrect number of bindings - 不能在 SQL 字符串里拼接字段名、表名、ORDER BY 子句:这些不属于“数据”,
?绑定无效,硬拼就是开后门 - 示例安全写法:
cursor.execute("SELECT value FROM sensors WHERE device_id = ? AND ts > ?", (device_id, last_sync_ts))
绝对不要碰 execute_script()
这个方法允许执行多语句、无视任何参数化机制,等价于把字符串直接喂给 SQLite 解析器。物联网固件里常见误用场景:
- 从 MQTT 主题收到一段 SQL 字符串,直接丢给
execute_script() - 用它动态建归档表:
execute_script("DROP TABLE IF EXISTS log_202512; CREATE TABLE ..."),而表名由设备时间生成却没校验格式 - OTA 下发的“SQL 配置片段”未经白名单过滤就执行
替代方案:DDL 操作全部硬编码;若真需动态表名,先用正则 ^[a-zA-Z][a-zA-Z0-9_]{1,31}$ 校验,再用 f"CREATE TABLE {table_name}" 拼接,且仅限可信上下文(比如启动时读取本地 JSON 配置)。
别指望 register_converter() 防注入
这个函数只是做类型转换桥接,不是过滤器。有人注册一个 JSON converter,在里面调 json.loads() 处理数据库字段,但如果该字段本身是攻击者可控的(比如存了恶意构造的 JSON 字符串),就可能触发反序列化漏洞或解析崩溃。
- 真正该做的是:在 Python 层用
json.loads()前,先检查字符串是否以{开头、长度是否合理、捕获json.decoder.JSONDecodeError -
register_converter()里绝不能出现eval()、exec()、未经清洗的json.loads() - 敏感字段加密应在应用层完成,SQLite 本身不加密
关闭错误回显 + 控制日志输出
轻量级设备上,sqlite3 报错信息(比如 no such table、near "OR": syntax error)一旦返回给前端或写进日志,就等于帮攻击者测绘数据库结构。
- 生产固件中禁用详细异常 traceback 输出
- 避免把原始 SQL 或参数值打到日志里(尤其含
device_id、token等) - 连接数据库时优先用只读模式:
sqlite3.connect("db.db", uri=True, flags=sqlite3.SQLITE_OPEN_READONLY)
最危险的不是不会写参数化查询,而是以为用了 executemany() 就安全,结果传了列表;或者觉得加了正则校验就能随便拼表名——这两类错误在嵌入式日志里反复出现,且极难被自动化扫描发现。











