必须使用?占位符参数化查询,禁用execute_script(),关闭错误回显并控制日志输出。

SQLite 的 execute() 和 executemany() 必须用参数占位符
SQLite 在嵌入式场景中常被直接集成进固件,但很多开发者仍习惯用字符串拼接构造查询,比如:cursor.execute("SELECT * FROM sensors WHERE id = " + str(device_id))。这种写法在 device_id 来自传感器上报或 OTA 配置时,极易被注入恶意值(如 1 OR 1=1),导致全表读取或配置篡改。
正确做法是只用 ? 占位符(SQLite 不支持命名参数如 :name,除非你显式启用并注册转换器):
cursor.execute("SELECT value FROM sensors WHERE device_id = ? AND ts > ?", (device_id, last_sync_ts))
注意:参数必须是元组(哪怕只有一个值也要写成 (value,)),不能是列表或单个值,否则会报 sqlite3.ProgrammingError: Incorrect number of bindings。
别碰 execute_script(),尤其不处理用户输入时
execute_script() 允许执行多条 SQL 语句,且完全绕过参数化机制——它本质就是字符串执行。物联网设备若用它加载动态配置脚本、升级 SQL 表结构,或解析 OTA 下发的 SQL 片段,等于主动打开注入后门。
常见踩坑场景:
- 从 MQTT 主题收到一段 SQL 字符串,直接丢给
execute_script() - 日志归档脚本里用
execute_script("DROP TABLE IF EXISTS log_202512; CREATE TABLE..."),而表名由设备时间生成却未校验格式
替代方案:所有 DDL 操作应硬编码或白名单控制;动态表名/字段名必须先通过正则校验(如 ^[a-zA-Z][a-zA-Z0-9_]{1,31}$),再用字符串格式化(非用户输入源)拼接,且仅限可信上下文。
Python 的 sqlite3.register_converter() 不解决注入,反而可能引入新风险
有些开发者试图用自定义 converter 处理 JSON 或二进制字段,比如注册一个 json converter 把数据库里的字符串自动转成 dict。这本身不防注入,反而在 converter 函数里做 eval() 或 json.loads() 未经清洗的字段时,可能触发反序列化漏洞或内存越界。
真正该做的只有两件事:
- 对所有传入
execute()的变量,坚持用?占位 - 对需要校验结构的字段(如 sensor_config 字段存 JSON),在 Python 层用
json.loads()前先检查是否为合法 JSON 字符串(用str.startswith('{')+ 长度限制 +json.decoder.JSONDecodeError捕获)
converter 是数据类型桥接工具,不是安全过滤器。
轻量级设备上禁用错误回显,别让 sqlite3.OperationalError 泄露表结构
嵌入式设备资源有限,但更不能返回原始 SQLite 错误。例如查询失败时打印 no such table: users 或 near "UNION": syntax error,攻击者能立刻推断出数据库设计和当前注入尝试是否成功。
务必做到:
- 捕获所有
sqlite3.Error,统一返回泛化错误码(如ERR_DB_ACCESS)或静默失败 - 关闭调试模式:确保生产固件中
sqlite3.enable_callback_tracebacks(False)已调用 - 不在日志里记录原始 SQL,尤其含参数值的完整语句(
print(f"Executing: {sql} with {params}")是高危操作)
最简防护线就三条:参数化、禁错误、控日志。其他像 WAF、ORM、复杂验证,在 MCU 级设备上既无必要也不现实。











