错误根源
每次在将动态内容与sql语句拼接时都要考虑sql注入的风险。问题根源在于没有将数据与代码(sql语句)进行分离,这个和xss很类似。示例:
sql = 'SELECT * FROM bugs WHERE bug_id = ' + '1; DELETE * FROM users;'
session.execute(sql)
防御方法
1. 过滤输入
过滤用户输入的不合法字符
如整数类型转换
bug_id = int(bug_id)
sql = 'SELECT * FROM bugs WHERE bug_id = %d;' % bug_id
如动态按列排序,假如表中字段名称只包含字母,数字和下划线
import re
order_by = re.sub(r'[^\w_]', '', order_by)
sql = 'SELECT * FROM bugs ORDER BY %s;' % order_by
# 用户即使输入 order_by = 'user; DELETE * FROM xxx;' 也会被转换为order_by = 'userDELETEFROMxxx'
# 执行错误但没有安全问题
2. 参数化动态内容
强有力的防御手段,对于动态数据十分有效。使用查询参数将数据与SQL表达式分离。参数化查询,数据库先将带?的SQL表达式编译好之后,才套用参数执行。如sqlite:
cursor.execute('INSERT INTO people VALUES (?, ?)', (who, age))
3. 数据与代码隔离
对于表名,字段名,sql关键字需要动态化的,可以使用隔离方式。建立用户输入与sql动态部分的映射关系,如字典。让用户界面与查询细节解耦,同时不将用户输入与sql语句拼接,降低风险。如:
field_map = {
'age': 'people_age',
'gender': 'people_gender'
...
}
# order_by = 'age'
order_by = field_map.get(order_by, 'default')
sql = 'SELECT * FROM people ORDER BY %s;' % order_by