为什么SQL查询不能直接用拼接的Cookie值来防注入?
- 内容介绍
- 文章标签
- 相关推荐
本文共计960个文字,预计阅读时间需要4分钟。
直接将 `Cookie` 中的值(例如 `session_id=abc123` 或 `user_token=xyz789`)用字符串拼接进 SQL 查询,等同于让攻击者任意控制查询逻辑。这并非可能被注入,而是必然可注入——因为 HTTP Cookie 完全由客户端控制,服务端未进行验证和参数化,所以任何客户端提供的 Cookie 值都会被直接用于查询,没有任何区分。
SELECT * FROM users WHERE token = ' + cookie_value + ' 是典型高危写法
这种拼接常见于老式登录校验或会话查找逻辑,例如在 Node.js 中用 mysql 模块、PHP 中用 mysqli::query、Java 中用 Statement.execute 等非参数化方式构造 SQL。
攻击者只需在 Cookie 中传入 user_token=' OR '1'='1,整条语句就变成:
SELECT * FROM users WHERE token = '' OR '1'='1'
结果是匹配全部用户,甚至配合注释符 -- 或分号 ; 还能执行多语句(取决于驱动是否启用多语句模式)。
- 即使
Cookie值经过 base64 编码或简单正则过滤(如只允许字母数字),也挡不住绕过:base64 解码后仍是可控字符串,正则漏掉'、;、/*等关键字符很常见 - 部分框架对
Cookie做了签名(如 Express 的signedCookies),但签名只防篡改,不解决拼接本身的问题——一旦签名密钥泄露或验证逻辑被绕过,照样崩 - ORM 如 Sequelize、TypeORM 默认也不自动防御拼接;若手动拼进
where字段(如where: `token = '${req.cookies.token}'`),照样中招
唯一安全做法:所有外部输入走参数化查询或预编译语句
无论来源是 URL、Body 还是 Cookie,只要是动态值,就必须脱离 SQL 字符串结构。
- Node.js(
pg):用client.query('SELECT * FROM users WHERE token = $1', [req.cookies.token]) - Python(
psycopg2):用cursor.execute("SELECT * FROM users WHERE token = %s", (request.cookies.get('token'),)) - PHP(
PDO):用$stmt = $pdo->prepare("SELECT * FROM users WHERE token = ?"); $stmt->execute([$token]); - Java(
JDBC):必须用PreparedStatement,绝不用Statement拼接
注意:参数化只保护「值」,不保护「标识符」(如表名、列名)。如果 Cookie 决定了查哪张表(如 tenant_id 动态选库),那得用白名单映射,不能拼接表名。
Cookie 值本身还要额外校验长度、格式和时效性
参数化解决注入,但不解决业务逻辑漏洞。比如 user_token 长度异常(500 字符)、含非法字符(控制符、Unicode 零宽空格)、时间戳明显过期,这些都该在进 SQL 前拦截。
- 用
crypto.timingSafeEqual()(Node)或hash_equals()(PHP)比对 token,防止时序攻击 - 查库前先检查 token 是否在 Redis 黑名单里(如用户登出后未失效)
- 避免把敏感字段(如密码哈希、密钥)直接存进 Cookie;应只存不可逆的随机令牌,并与服务端 session 绑定
最常被忽略的一点:开发时习惯用 Postman 或浏览器插件手动设 Cookie 测试,容易忘记它和真实攻击面完全一致——那个你随手填的 admin=true,就是攻击者第一批尝试的 payload。
本文共计960个文字,预计阅读时间需要4分钟。
直接将 `Cookie` 中的值(例如 `session_id=abc123` 或 `user_token=xyz789`)用字符串拼接进 SQL 查询,等同于让攻击者任意控制查询逻辑。这并非可能被注入,而是必然可注入——因为 HTTP Cookie 完全由客户端控制,服务端未进行验证和参数化,所以任何客户端提供的 Cookie 值都会被直接用于查询,没有任何区分。
SELECT * FROM users WHERE token = ' + cookie_value + ' 是典型高危写法
这种拼接常见于老式登录校验或会话查找逻辑,例如在 Node.js 中用 mysql 模块、PHP 中用 mysqli::query、Java 中用 Statement.execute 等非参数化方式构造 SQL。
攻击者只需在 Cookie 中传入 user_token=' OR '1'='1,整条语句就变成:
SELECT * FROM users WHERE token = '' OR '1'='1'
结果是匹配全部用户,甚至配合注释符 -- 或分号 ; 还能执行多语句(取决于驱动是否启用多语句模式)。
- 即使
Cookie值经过 base64 编码或简单正则过滤(如只允许字母数字),也挡不住绕过:base64 解码后仍是可控字符串,正则漏掉'、;、/*等关键字符很常见 - 部分框架对
Cookie做了签名(如 Express 的signedCookies),但签名只防篡改,不解决拼接本身的问题——一旦签名密钥泄露或验证逻辑被绕过,照样崩 - ORM 如 Sequelize、TypeORM 默认也不自动防御拼接;若手动拼进
where字段(如where: `token = '${req.cookies.token}'`),照样中招
唯一安全做法:所有外部输入走参数化查询或预编译语句
无论来源是 URL、Body 还是 Cookie,只要是动态值,就必须脱离 SQL 字符串结构。
- Node.js(
pg):用client.query('SELECT * FROM users WHERE token = $1', [req.cookies.token]) - Python(
psycopg2):用cursor.execute("SELECT * FROM users WHERE token = %s", (request.cookies.get('token'),)) - PHP(
PDO):用$stmt = $pdo->prepare("SELECT * FROM users WHERE token = ?"); $stmt->execute([$token]); - Java(
JDBC):必须用PreparedStatement,绝不用Statement拼接
注意:参数化只保护「值」,不保护「标识符」(如表名、列名)。如果 Cookie 决定了查哪张表(如 tenant_id 动态选库),那得用白名单映射,不能拼接表名。
Cookie 值本身还要额外校验长度、格式和时效性
参数化解决注入,但不解决业务逻辑漏洞。比如 user_token 长度异常(500 字符)、含非法字符(控制符、Unicode 零宽空格)、时间戳明显过期,这些都该在进 SQL 前拦截。
- 用
crypto.timingSafeEqual()(Node)或hash_equals()(PHP)比对 token,防止时序攻击 - 查库前先检查 token 是否在 Redis 黑名单里(如用户登出后未失效)
- 避免把敏感字段(如密码哈希、密钥)直接存进 Cookie;应只存不可逆的随机令牌,并与服务端 session 绑定
最常被忽略的一点:开发时习惯用 Postman 或浏览器插件手动设 Cookie 测试,容易忘记它和真实攻击面完全一致——那个你随手填的 admin=true,就是攻击者第一批尝试的 payload。

