SQLite 注入(SQL Injection) 是指攻击者通过 恶意构造输入,绕过程序逻辑,执行非预期的 SQL 语句,从而 读取、修改、删除数据库数据,甚至 获取系统权限。
虽然 SQLite 是嵌入式数据库,但 同样存在注入风险,尤其在动态拼接 SQL 时。
一、注入原理(以 SQLite 为例)
危险写法(拼接字符串)
name = "Alice'; DROP TABLE users; --"
sql = f"SELECT * FROM users WHERE name = '{name}'"
# 最终 SQL:
# SELECT * FROM users WHERE name = 'Alice'; DROP TABLE users; --'
执行后:
- 查询 Alice
- 删除整个 users 表!
二、常见注入类型
| 类型 | 示例输入 | 效果 |
|---|---|---|
| 经典注入 | ' OR '1'='1 | 登录绕过 |
| 注释注入 | admin'-- | 注释掉后续条件 |
| 堆叠注入 | 1; DROP TABLE users; | 执行多条语句 |
| UNION 注入 | ' UNION SELECT sqlite_version(), NULL-- | 泄露版本 |
| 时间盲注 | ' AND (SELECT sqlite_version() LIKE '3%') AND '1'='1 | 判断版本 |
三、SQLite 特有注入点
| 特性 | 注入风险 |
|---|---|
ATTACH DATABASE 'file.db' AS aux | 文件写入(需权限) |
PRAGMA table_info(...) | 泄露表结构 |
sqlite_master 表 | 读取所有表/视图/索引 |
LOAD_EXTENSION() | 加载恶意扩展(需启用) |
示例:读取所有表名
' UNION SELECT name, sql FROM sqlite_master WHERE type='table'--
四、如何防御(重点!)
1. 使用参数化查询(预编译语句) ← 最有效
Python 示例(sqlite3 模块)
import sqlite3
conn = sqlite3.connect('app.db')
cursor = conn.cursor()
name = "Alice'; DROP TABLE users; --"
# 正确:使用 ? 占位符
cursor.execute("SELECT * FROM users WHERE name = ?", (name,))
即使输入是恶意字符串,也被当做 纯文本,不会执行
其他语言
| 语言 | 正确写法 |
|---|---|
| Node.js | db.get("SELECT * FROM users WHERE name = ?", [name]) |
| Java | PreparedStatement pst = conn.prepareStatement("SELECT ... WHERE name = ?"); pst.setString(1, name); |
| PHP | $stmt = $pdo->prepare("SELECT ... WHERE name = ?"); $stmt->execute([$name]); |
2. 避免动态拼接 SQL
# 危险
sql = f"SELECT * FROM users WHERE name = '{user_input}'"
# 安全
cursor.execute("SELECT * FROM users WHERE name = ?", (user_input,))
3. 输入验证 + 白名单
import re
def is_valid_username(name):
return bool(re.match("^[a-zA-Z0-9_]{3,20}$", name))
if not is_valid_username(name):
raise ValueError("Invalid username")
4. 最小权限原则
- 不要用
root或高权限用户连接数据库 - 生产环境 禁用
ATTACH DATABASE - 限制文件系统写入权限
-- 禁用危险函数(应用层控制)
PRAGMA secure_delete = ON;
-- 不要启用 load_extension
5. 使用 ORM(自动防注入)
| ORM | 示例 |
|---|---|
| SQLAlchemy | session.query(User).filter(User.name == name).first() |
| Peewee | User.get(User.name == name) |
| Django ORM | User.objects.get(name=name) |
自动使用参数化查询
五、检测是否可注入(测试技巧)
# 1. 经典测试
' OR '1'='1
' OR 1=1--
# 2. 报错注入
' AND sqlite_version()--
# 3. 时间盲注
' AND (SELECT COUNT(*) FROM sqlite_master)>0 AND sleep(5)--
# 4. UNION 测试列数
' UNION SELECT 1--
' UNION SELECT 1,2--
' UNION SELECT 1,2,3--
六、真实案例分析
场景:登录绕过
-- 后端 SQL(错误)
SELECT * FROM users WHERE username = '{input}' AND password = '{pwd}'
-- 输入:
username: admin'--
password: anything
-- 最终 SQL:
SELECT * FROM users WHERE username = 'admin'--' AND password = '...'
→ 登录成功!
修复:
cursor.execute(
"SELECT * FROM users WHERE username = ? AND password = ?",
(username, password)
)
七、SQLite 安全配置建议
-- 1. 生产环境关闭调试
PRAGMA query_only = ON; -- 禁止写操作(只读模式)
-- 2. 加密数据库(SQLCipher)
-- 3. 限制文件权限
chmod 600 database.db
-- 4. 定期备份 + 版本控制
八、快速自检清单
| 检查项 | 是否安全 |
|---|---|
使用 ? 或命名参数 | ✅ |
| 动态拼接 SQL | ❌ |
| 输入未验证 | ❌ |
| 使用 ORM | ✅ |
允许 ATTACH DATABASE | ⚠️ |
| 数据库文件可被外部访问 | ❌ |
总结:防注入黄金法则
永远不要信任用户输入
永远不要拼接 SQL 字符串
永远使用参数化查询
# 正确模板
cursor.execute("SELECT ... WHERE col = ?", (value,))
# 或
cursor.execute("INSERT INTO t VALUES (?, ?)", (a, b))
需要我帮你:
- 审计一段代码是否存在注入风险?
- 把动态 SQL 改写为参数化版本?
- 生成安全的用户登录/注册 SQL?
把你的代码或 SQL 发我!