
1) 【一句话结论】在安全开发中,防止SQL注入的核心是避免动态拼接SQL语句,通过参数化查询(预编译语句)或严格转义特殊字符,确保用户输入仅作为数据而非SQL代码执行。
2) 【原理/概念讲解】SQL注入的本质是恶意用户利用应用对用户输入的信任,构造包含特殊字符(如单引号、分号)的输入,改变原SQL语句的语义(例如将“SELECT * FROM users”改为“SELECT * FROM users; DROP TABLE users;”)。参数化查询(如PreparedStatement)通过将SQL模板与参数分离,数据库引擎预先编译SQL结构,参数作为独立数据传递,不会影响SQL语法解析。类比:就像填空题,你把问题(SQL模板)和答案(用户输入)分开,答案不会改变问题的结构,即使答案里有“; DROP TABLE”这样的内容,也不会影响问题本身的语法。
3) 【对比与适用场景】
| 方法 | 定义 | 特性 | 使用场景 | 注意点 |
|---|---|---|---|---|
| 手动转义 | 对用户输入中的特殊字符(如单引号)进行转义(如替换为\') | 需要手动处理所有特殊字符,易遗漏,依赖开发者经验 | 简单场景(如静态SQL,少量参数) | 风险高,适用于复杂SQL时易出错 |
| 参数化查询 | 将SQL模板与参数分离,参数作为独立数据传递 | 数据库引擎预先编译SQL结构,参数不影响语法 | 大多数场景(尤其是动态SQL,多参数) | 需要支持参数化查询的数据库驱动 |
4) 【示例】
存在风险的代码(伪代码):
# 风险代码
user_id = request.get('id') # 假设从请求获取用户输入
sql = f"SELECT * FROM users WHERE id = '{user_id}'"
result = db.execute(sql) # 执行SQL
修改后的安全代码(使用参数化查询,以Python的psycopg2为例):
# 安全代码
user_id = request.get('id')
sql = "SELECT * FROM users WHERE id = %s"
result = db.execute(sql, (user_id,)) # 参数作为元组传递
原理:原代码将用户输入user_id直接拼接进SQL字符串,恶意输入如' OR '1'='1会导致SQL变为SELECT * FROM users WHERE id = '' OR '1'='1',绕过条件筛选。修改后,%s是占位符,数据库引擎会先编译SELECT * FROM users WHERE id = ?,再将user_id作为独立数据传递,无论输入是什么,都不会改变SQL的语法结构,从而防止注入。
5) 【面试口播版答案】好的,面试官,关于如何通过代码审查防止SQL注入,核心结论是避免动态拼接SQL语句,通过参数化查询(预编译语句)或严格转义特殊字符,确保用户输入仅作为数据而非SQL代码执行。
原理上,SQL注入的本质是恶意用户构造特殊字符(如单引号、分号)改变SQL语义。参数化查询通过将SQL模板与参数分离,数据库引擎预先编译SQL结构,参数作为独立数据传递,不会影响SQL语法解析,比如填空题,把问题(SQL模板)和答案(用户输入)分开,答案不会改变问题的结构。
对比来看,手动转义需要手动处理所有特殊字符,易遗漏,而参数化查询更安全,适用于大多数动态SQL场景。
举个例子,存在风险的代码是直接拼接用户输入:sql = f"SELECT * FROM users WHERE id = '{user_id}'",当用户输入' OR '1'='1时,SQL会变成SELECT * FROM users WHERE id = '' OR '1'='1',绕过条件筛选。修改后用参数化查询,比如sql = "SELECT * FROM users WHERE id = %s",将用户输入作为参数传递,数据库先编译SQL结构,再处理参数,这样无论输入什么,都不会改变SQL的语法,从而防止注入。
6) 【追问清单】
7) 【常见坑/雷区】
sql = "SELECT * FROM users WHERE id = %s"但执行时db.execute(sql, user_id)未正确传递参数,或使用%s但数据库驱动不支持,导致实际仍为拼接)。123拼接后id = 123是安全的,但'123会注入)。