大家好,今天我们继续来看SQL注入防护系列,WHERE IN 子句因为常用来处理逗号分隔的参数列表,也就成了攻击者的高频目标,今天我们将分析一下这类漏洞的成因、供给手法,和常见的解决方案。很多开发者觉得只要参数是数字,例如1,2,3就不会有问题,但是攻击者可以通过构造特殊的payload来突破限制,利用 ) 闭合 IN 子句的括号,再注入UNION查询语句,最后用--注释掉后续的无效代码。让我们看个例子:category_ids = "1) UNION SELECT id, username, password_hash FROM admin_users--"
SELECT id, name, price FROM products WHERE category_id IN (1) UNION SELECT id, username, password_hash FROM admin_users-- ↑ ↑ ↑↑ 闭合IN子句 注入UNION查询 注释掉原语句剩余的)
执行结果就是,可以直接读取管理员表admin_users的账号、密码哈希等敏感数据。那么要如何来解决呢?针对动态IN子句,核心是参数化查询,让我们以Python Flask + pyMysql的示例来看看是如何修复的:import pymysqlfrom flask import Flask, request, jsonifyapp = Flask(__name__)@app.route('/products/filter', methods=['GET'])def filter_products(): # 1. 获取前端传入的分类ID参数,默认值为1 category_ids = request.args.get('categories', '1') # 2. 拆分参数并严格验证类型(仅允许整数) id_list = category_ids.split(',') try: # 去除空格并强制转换为整数,非数字直接抛出异常 id_list = [int(x.strip()) for x in id_list] except ValueError: # 验证失败返回400错误,阻断恶意输入 return jsonify({"error": "Invalid category IDs"}), 400 # 3. 动态生成与参数数量匹配的占位符(避免拼接SQL) placeholders = ','.join(['%s'] * len(id_list)) # 4. 建立数据库连接并执行参数化查询 conn = pymysql.connect( host='localhost', user='app', password='pass', database='shop' ) cursor = conn.cursor() # 关键:SQL语句使用占位符,参数通过execute第二个参数传入 query = f"SELECT id, name, price FROM products WHERE category_id IN ({placeholders})" cursor.execute(query, tuple(id_list)) # 自动转义参数,杜绝注入 # 5. 获取结果并关闭连接 results = cursor.fetchall() conn.close() return jsonify({"products": results})if __name__ == '__main__': app.run(debug=False) # 生产环境务必关闭debug模式
它的核心思路就是,强制将category_ids转换为整数,只要不是数字直接拒绝;根据参数数量动态生成%s占位符,避免直接拼接SQL语句;最后通过cursor.execute(query,参数元组)方式传递参数,数据库驱动会自动转义特殊字符,彻底阻断注入。虽然,我们使用的是Python示例,但是核心逻辑在其它的语言应用中国同样适用,以后再有类似的情况,可以尝试着试试。