游客发表
登录框里输入 `' OR '1'='1`,参数很多年前就能轻松绕过一批网站的化查后台验证。这不是参数什么高深攻击,本质上是化查程序员把用户输入直接拼进了SQL语句里。数据库看到这个字符串,参数分不清哪部分是化查代码、哪部分是参数数据,于是化查乖乖执行了 `SELECT * FROM users WHERE username='' OR '1'='1'`,整张表都被翻了出来。参数
参数化查询解决的化查就是这个问题。它的参数做法是先把SQL语句的骨架发给数据库,用占位符标出需要填值的化查位置,然后再把用户数据单独传过去。参数数据库收到两次信息,化查一次是参数"我要查什么",一次是"条件值是什么",两者不会混在一起解析。即使用户输入里含有SQL关键字,数据库也只把它当作普通字符串处理。
我第一次真正理解这个机制,是在调一个Java项目的时候。当时用 PreparedStatement 写了一条查询:
String sql = "SELECT * FROM orders WHERE user_id = ? AND status = ?"; PreparedStatement ps = connection.prepareStatement(sql); ps.setInt(1, userId); ps.setString(2, status); ResultSet rs = ps.executeQuery(); 两个问号就是占位符。数据库驱动在底层会把这条语句先编译好执行计划,然后才把具体的 userId 和 status 值绑定上去。整个过程里,即使用户传的 status 是 "'; DROP TABLE orders; --",数据库也只会拿它去匹配 status 字段的值,不会把它当成SQL指令来跑。
不同语言和框架里,参数化查询的写法不太一样。Python 的 sqlite3 模块用 ? 做占位符,cursor.execute("SELECT * FROM users WHERE name = ?", (name,))。PHP 的 PDO 支持命名参数,写成 :name 的形式。Node.js 的 mysql2 库同样用 ?。形式有差异,核心逻辑一致:语句模板和数据分开传输。
有一种常见的错误做法需要特别点出来。有人确实用了占位符语法,但拼接方式不对。比如在 Python 里写成 cursor.execute("SELECT * FROM users WHERE name = '%s'" % name),这仍然是把变量直接塞进字符串,跟参数化查询没有半点关系。占位符必须作为参数传给 execute 方法的第二个参数,而不是用字符串格式化提前替换掉。这个区别很关键,我见过不少初学者在这里翻车。
另一个容易被忽略的点是,参数化查询并非万能。它主要防的是SQL注入里通过数据输入发起的攻击。如果SQL语句的结构本身是动态拼接的,比如用户可以选择按哪个字段排序、升序还是降序,那 ORDER BY 后面的内容就没法用占位符处理。这种场景需要额外的白名单校验,只允许预定义的字段名和排序方向通过。参数化查询管不了这部分,得靠开发者自己补上。
性能方面也值得说一句。很多人以为参数化查询只是安全措施,其实它还有执行计划复用的好处。同一条SQL模板,换个参数值再执行,数据库可以重用之前编译好的执行计划,省去重新解析和优化的开销。批量插入几千条数据的时候,这个差距相当明显。当然,这取决于数据库实现和驱动行为,不是所有场景都能吃到这个红利,但至少不会比拼接字符串更慢。
回到安全本身。SQL注入在OWASP Top 10里待了很多年,位置有升有降,但从未跌出榜单。参数化查询是防御它的第一道防线,也是成本最低的一道。写代码的时候多打一个问号,比事后补漏洞、擦数据、发公告要省事得多。这个习惯值得每个跟数据库打交道的人养成。
友情链接