Kodokon kodokon.com

安全:SQL 注入、权限、预处理语句

重现一次 SQL 注入,用预处理语句将其化解,并应用最小权限原则。

11 分钟 · 3 题

在 Kodokon 中打开本课

SQL 注入发生在不可信数据被拼接进查询文本的那一刻。攻击者提供一个会闭合字符串并劫持逻辑的值。让我们在一张 users 表上重现这次攻击:直接拼接输入 ada' OR '1'='1,会把一个精确的过滤变成一个恒真的条件。

SQL
CREATE TABLE users (
  id INTEGER PRIMARY KEY,
  name TEXT NOT NULL,
  is_admin INTEGER NOT NULL DEFAULT 0
);

INSERT INTO users (id, name, is_admin)
VALUES (1, 'ada', 0), (2, 'root', 1);

-- Dangerous concatenation (NEVER do this)
-- input = ada' OR '1'='1
SELECT * FROM users
WHERE name = 'ada' OR '1'='1';
被注入的查询返回了所有的行。

这个载荷之所以奏效,是因为引号过早地闭合了字面量 'ada',接着 OR '1'='1' 追加了一个普遍为真的条件。一个变体是在末尾附加 -- 来把原查询余下的部分注释掉。教训在于:只要数据和代码共用同一个字符串,任何手动转义都不是真正可靠的。

SQL
-- Positional placeholder
SELECT * FROM users WHERE name = ?;

-- Named placeholder
SELECT * FROM users WHERE name = :name;
SQLite 中绑定参数的两种形式。

补救办法是预处理语句:驱动先编译查询的结构,然后单独绑定这些值。被绑定的数据永远不会被当作 SQL 来解析,只会被用于比较。作为额外的好处,编译好的结构是可复用的:接连绑定多个值,可以避免每次调用都重新编译计划。

SQL
-- The bound value stays data, not SQL
SELECT * FROM users
WHERE name = :name;
-- :name = ada' OR '1'='1
-- -> 0 rows: no user
--    is literally named that
同一个载荷,如今已无害。

知识检测

确认你已牢记本课的重点内容。

  1. 为什么带参数的预处理语句能防止 SQL 注入?
    • 因为它对传输的值进行了加密
    • 因为被绑定的值被当作数据,绝不会被解析为 SQL
    • 因为它自动转义了字符串中的引号
    • 因为它限制了用户输入的长度
  2. 载荷 ' OR '1'='1 之所以能注入成功,是因为它……
    • 耗尽了数据库服务器的内存
    • 闭合了字符串字面量,然后追加了一个恒真的条件
    • 删除了查询所针对的表
    • 改变了当前用户的权限
  3. 你能通过一个 ? 参数来绑定表名吗?
    • 能,占位符接受任何类型的标识符
    • 不能:占位符只绑定值;标识符必须对照白名单来校验
    • 能,但只能用命名形式 :name
    • 能,只要该表已经存在