如何利用标记模板字符串(Tagged Templates)防御 SQL 注入与 XSS需要先看清适用场景和关键步骤,避免只记结论却忽略实际限制。
模板字符串本身不防SQL/XSS,因${}仅作字符串拼接无转义;真正安全需自定义标签函数,如sql...${x}...生成?占位符与参数数组交由驱动执行。
很多人误以为用了模板字符串就“天然安全”,其实完全相反:`SELECT * FROM users WHERE id = ${id}` 和拼接字符串一样危险。JavaScript 模板字符串只是语法糖,${} 里的值会直接转成字符串并插入,没有任何转义或上下文感知。SQL 注入和 XSS 的本质是“数据被当成了代码执行”,而模板字符串根本不区分数据与代码——它只负责拼。
真正起作用的是标签函数(tag function),它能拦截模板字面量和插值参数,拿到原始字符串数组和动态值,从而在拼接前做上下文相关的处理。关键点在于:你必须自己写/用可靠的标签函数,且它得知道当前目标是 SQL 还是 HTML。
? 占位符),再把值单独传给数据库驱动,绝不能拼进字符串& → &),但注意:不能编码整个模板字符串,否则破坏结构;只编码插值部分sql`...${x}...` 和 html`<div>${x}</div>` 的区别全靠函数名和实现逻辑,不是语法强制的下面这个 sql 标签函数不执行查询,只生成带 ? 占位符的语句和参数数组,交由底层驱动(如 mysql2、pg)安全执行:
function sql(parts, ...values) { let query = parts[0]; const params = []; for (let i = 0; i < values.length; i++) { query += '?' + parts[i + 1]; params.push(values[i]); } return { text: query, params };}<p>// 使用示例const id = "1; DROP TABLE users; --";const result = sql<code>SELECT * FROM users WHERE id = ${id} AND active = ${true}</code>;// → { text: "SELECT * FROM users WHERE id = ? AND active = ?", params: ["1; DROP TABLE users; --", true] }
注意:result 不能直接传给 exec(),必须交给支持参数化查询的驱动。若你硬用 eval() 或字符串拼接去“执行” result.text,等于白做。
Tagged Templates 不是银弹。最容易被忽略的几点:
String(value) 再拼进 SQL 字符串,就立刻失效——必须坚持参数分离,绝不拼值<span>OK</span>),用 html 标签函数会把它双编码成 <span>OK</span>,反而显示为源码。这时需要显式标记“已信任”,例如 html`<div>${raw(trustedHtml)}</div>`
${userInput} 没包在 sql`` 或 html`` 里,整条链就破了)和 JS Tagged Templates 无关,别混用真正的防线不在语法糖,而在是否始终把数据和代码的边界守死——Tagged Templates 只是帮你把这道边界显式地写在调用处。