当AI智能体被赋予直接操作数据库的能力,例如根据用户提问自动生成并执行SQL语句时,系统就引入了一类隐蔽但致命的风险:SQL注入漏洞。许多智能体为了快速实现功能,会把用户的问题原文拼进SQL字符串再交给数据库执行,这种做法一旦被刻意诱导,就会让攻击者通过自然语言中的特殊片段改变原有查询语义。理解这类漏洞的产生机制,是构建安全Agent系统的第一步。

AI智能体SQL注入漏洞的常见产生路径
在典型的Agent架构中,大模型负责将用户的自然语言转换为SQL语句,随后通过一段执行器代码发送到数据库。如果执行器采用了最原始的字符串格式化方式,例如Python里用f-string把用户意图和表名、条件拼在一起,那么用户输入中只要包含单引号、注释符或者union关键字,就能突破开发者的预设逻辑。比如用户问“显示订单,顺便把密码表也查出来”,模型若缺乏约束就可能生成跨表查询。
更危险的是,一些智能体为了提升准确率,会把历史对话上下文也作为SQL生成的参考。当上下文里混入了外部不可信内容,例如网页抓取结果或第三方插件返回值,注入点就从单一用户输入扩散到整个上下文链路。此时仅靠提示词约束模型“不要写危险SQL”是远远不够的,因为模型本身不具备对数据库权限边界的刚性理解,它只是按概率补全文本。
从代码层面看,漏洞往往藏在看似无害的封装函数里。下面这段Node.js代码展示了智能体执行器中最容易出问题的写法:把用户条件直接拼进查询字符串。它短期能跑通功能,却为后续攻击敞开了大门。
// 危险示例:AI智能体直接拼接用户条件
async function runAgentQuery(userQuestion) {
const sql = "SELECT * FROM orders WHERE note LIKE '%" + userQuestion + "%'";
const result = await db.query(sql);
return result;
}
// 若 userQuestion 为:%' UNION SELECT username,password FROM users --
// 最终SQL将泄露用户表
基于日志与静态分析的检测方法
检测AI智能体的SQL注入风险,第一步应当建立查询日志的集中采集。由于Agent生成的SQL具有动态性和不可预测性,传统基于固定规则的WAF很难覆盖,因此需要把每次模型输出、最终执行SQL、触发用户ID记录下来。通过离线分析,可以找出包含union select、sleep(、/**/以及异常注释符--的语句,这些往往是注入尝试或模型越权生成的信号。
除了运行时日志,静态代码审计也必不可少。开发团队应当扫描执行器代码中所有出现字符串拼接数据库语句的位置,重点检查是否使用了模板字符串、加号连接或者格式化函数。配合调用链分析,确认这些拼接片段是否可追溯到大模型输出。下表列出了几种常见写法及其风险等级:
| 代码写法 | 风险等级 | 说明 |
|---|---|---|
| 字符串相加拼SQL | 高 | 用户输入可改变语句结构 |
| 模板字符串嵌入变量 | 高 | 本质仍为拼接,无类型保护 |
| 使用ORM占位符 | 低 | 参数与语句分离,安全 |
| 预编译语句传参 | 低 | 驱动层绑定,防注入 |
对于已经上线的智能体,还可以采用流量回放检测:把历史用户问题重新喂给Agent,观察其生成的SQL是否在某些输入下发生结构性突变。如果同一个语义的问题仅因标点或措辞变化就产出截然不同的SQL形态,说明模型输出稳定性不足,也容易成为注入突破口。此时需要结合提示词工程与输出校验双管齐下。
使用参数化查询彻底修复漏洞
参数化查询之所以能根除SQL注入,是因为它把SQL指令本身和数值数据分成两个通道发送给数据库。指令模板在预编译阶段就确定了语法树结构,用户输入只能作为叶子节点的字面值绑定进去,无法篡改select、where等关键字关系。在AI智能体场景里,应当要求模型只输出结构化参数,而不是完整SQL文本,由执行器拼装安全语句。
以Python的sqlite3为例,下面代码展示了修复后的写法。模型只需提供表名白名单与参数字典,执行器使用问号占位符,彻底杜绝拼接。即使模型被诱导输出了恶意字符串,数据库也只会把它当作普通查询值处理。
import sqlite3
def safe_agent_query(table, field, value):
# 表名需白名单校验,不可来自用户
allowed = {"orders": True, "logs": True}
if table not in allowed:
raise ValueError("非法表名")
conn = sqlite3.connect("app.db")
cur = conn.cursor()
# 参数化占位符,value来自模型提取的用户条件
sql = "SELECT id FROM " + table + " WHERE " + field + " = ?"
cur.execute(sql, (value,))
return cur.fetchall()
在Java生态中,使用PreparedStatement也是同样思路。智能体后端应避免任何Statement.execute(String)的直接调用,全部改为预编译加setString、setInt等类型绑定方法。同时,对于表名、列名这类无法参数化的部分,必须建立服务端白名单映射,绝不允许模型自由指定。只有做到指令与数据分离、动态部分白名单化,AI智能体的数据库操作才具备生产环境所需的安全底线。
智能体输出层的额外防护策略
即便后端全部改为参数化,AI智能体仍可能通过生成越权查询逻辑造成数据泄露,例如把本该查自己的条件替换成查全表。因此在模型输出环节要加入语法与语义双重校验:先用SQL解析器确认语句只涉及授权表与只读操作,再比对生成条件是否脱离用户会话身份。任何不满足约束的SQL都应在执行前驳回并重新追问。
实践上可以引入轻量级的SQL语法白名单,限制Agent只能产出带特定别名的查询模板,把自由度锁死在业务需要的范围内。配合参数化执行器,形成“模型出参受控、执行入参安全”的双保险。经过这类改造的系统,在红队演练中面对精心构造的注入提问时,能够稳定返回拒绝执行或安全空结果,而不是泄露底层数据。
// Java示例:PreparedStatement防止注入 String sql = "SELECT name FROM user WHERE id = ?"; PreparedStatement ps = conn.prepareStatement(sql); ps.setInt(1, userId); // 类型绑定,用户输入无法改变SQL结构 ResultSet rs = ps.executeQuery();
最后需要强调的是,安全是一个持续过程。智能体的模型版本、提示词、插件生态都会演进,团队应把SQL注入检测纳入常规回归测试,每次模型更新都跑一遍注入语料库。只有把参数化修复和输出治理变成基础设施,才能让AI智能体在享受自然语言交互便利的同时,不把数据库大门向攻击者敞开。