真正有效的SQL注入验证是确认查询是否走参数化执行通道,而非字符串拼接或过滤;须检查代码中是否存在拼接SQL的写法,使用占位符并传参才启用预编译,动态字段必须白名单校验。

SQL注入验证:别用字符串拼接测,直接看参数化执行路径
手动拼接 SQL 字符串再“过滤”特殊字符,本质上无法验证安全性——因为过滤逻辑永远追不上数据库解析器的实际行为。真正有效的验证,是确认查询是否走参数化执行通道。
- 检查代码中是否出现
cursor.execute("SELECT * FROM user WHERE id = " + user_id)这类拼接,只要存在,就等于没安全 - Python 的
sqlite3、psycopg2、mysql-connector-python都只在使用%s(MySQL/PostgreSQL)或?(SQLite)占位符且传入参数元组/字典时,才启用预编译;写成"%s" % user_id或.format()仍是拼接 - Node.js 的
pg库必须用client.query("SELECT * FROM user WHERE id = $1", [id]),而非client.query(`SELECT * FROM user WHERE id = ${id}`) - Java 的
PreparedStatement必须调用setString(1, userInput),不能用statement.executeQuery("SELECT ... WHERE name = '" + name + "'")
标准库替代方案:按语言选对模块,不是选“最熟”的
用错库比不用更危险。比如 Python 项目里引入 sqlparse 做“SQL清洗”,它根本不参与执行,纯文本分析,对运行时注入零防护。
- Python:优先用数据库驱动原生支持的参数化,如
psycopg2的%s、sqlite3的?;ORM 如SQLAlchemy要确保用session.execute(text("..."), {"param": value})而非session.execute(f"..." % value) - Node.js:
pg(PostgreSQL)、mysql2(MySQL)原生支持参数化;禁用mysql(老版,易误用拼接) - Go:
database/sql的Query方法配合?占位符,驱动(如github.com/lib/pq)会自动转义;避免用fmt.Sprintf拼接 SQL - .NET:
SqlCommand必须用Parameters.AddWithValue(),禁用$"SELECT ... WHERE id = {id}"
容易踩的坑:看似参数化,实则绕过预编译
很多“参数化”写法只是表面合规,底层仍触发字符串替换。这类漏洞往往在动态表名、列名、ORDER BY 子句中爆发。
-
ORDER BY ?在大多数驱动中不被支持,psycopg2会报错,sqlite3会静默转为字符串拼接——此时必须白名单校验字段名,如if sort_field not in ["created_at", "name"]: raise ValueError - 拼接表名/列名时,哪怕用了
format()或 f-string +re.match(r"^[a-zA-Z_][a-zA-Z0-9_]*$", name),也扛不住 Unicode 变体、零宽空格等绕过手段,唯一可靠方式是查配置字典:table_map = {"user": "users", "order": "orders"} - PHP 的
PDO::prepare()若开启PDO::ATTR_EMULATE_PREPARES = true(默认 MySQL),会退化为客户端模拟预编译,遇到WHERE name = ?仍可能被注入——必须显式设为false
验证是否真安全:用单引号和分号触发语法错误
最简验证法:输入 ' OR 1=1; -- ,观察返回结果和数据库日志。如果返回了全部用户,或日志里出现两条语句(如 SELECT ...; DROP TABLE users;),说明没走参数化。
- 成功防护的表现:数据库报错类似
invalid input syntax for integer: "' OR 1=1; -- "(类型强校验)或no such column: ' OR 1=1; --(字符串被整体当值处理) - 注意中间件干扰:某些 Web 框架(如 Express 的
body-parser)会自动 trim 或 decode 输入,导致测试 payload 被修改,建议直接 curl 发 raw 数据验证 - 不要依赖 WAF 或代理层拦截来“验证安全”——它们只能拦已知模式,而参数化失败是根本性设计缺陷
参数化不是加个问号就完事,关键在执行路径是否真正隔离了代码与数据。表名、排序字段、UNION 查询这些地方,标准库通常不覆盖,得靠白名单+严格校验兜底。漏掉一个动态片段,整条链路就垮了。

















