去年有安全研究员在Twitter发截图——某知名电商搜索框输入一串单引号,整页用户密码直接蹦出。评论区有人问:不是用了ORM吗?怎么还会这样?
这个问题,我们用一段真实可复现的代码来回答。
![]()
这是一个专门学习Web安全的开源项目,技术栈是Next.js + Prisma。Prisma作为Node.js生态最受欢迎的ORM之一,确实能在大多数时候挡住SQL注入,但前提是你别主动绕过它的防护机制。而这个项目的搜索功能,恰恰踩了这颗雷。
实验环境:3分钟搭好靶场
想亲手复现漏洞,有两条路。本地Node.js环境:
npx create-oss-store oss-store
cd oss-store
npm run dev
或直接用Docker,连Node都不用装:
docker run -p 3000:3000 leogra/oss-oopssec-store
服务启动后访问 http://localhost:3000,你会看到功能完整的电商界面。商品列表、购物车、结账流程一应俱全。目标藏在最显眼的地方——顶部导航栏的搜索框。
攻击面分析:搜索框里的字符串拼接
搜索框调用的接口很直接:
/api/products/search?q=<搜索词>
用户输入什么,后端就搜什么。听起来正常,直到看一眼实现方式:后端收到q参数后,没有做任何转义或参数化处理,直接把字符串塞进SQL语句的LIKE子句。这意味着输入的每个字符——包括单引号、注释符、分号——都会原封不动变成SQL代码的一部分。
这种设计给攻击者留了敞开的门:用单引号闭合原本的查询上下文,后面就能接上任意SQL语句。典型的UNION注入场景。
第一步:确认注入点
先做正常搜索,输入fresh,能看到包含该关键词的商品列表,界面正常渲染,说明端点确实在用q参数查数据库。
现在换payload:
' UNION SELECT 1,2,3,4,5--
如果页面没报错,反而正常显示了一些"商品",注入成功。这个payload的工作原理是:前面单引号闭合原本的LIKE '%xxx%',后面UNION SELECT把5个常量合并进结果集,双横线注释掉原本语句的剩余部分。页面能渲染,说明后端完全信任了用户输入,把它当代码执行。
第二步:拖库实战
确认注入点后,下一步拿真实数据。需要知道users表有哪些列,以及它们的顺序。从代码里能看到,原始查询选了5列:id、name、description、price、imageUrl。所以UNION SELECT也要凑5列,类型可以混,但数量必须对齐。
最终payload:
DELIVERED' UNION SELECT id, email, password, role, addressId FROM users--
DELIVERED是商品名里的关键词,让前半句不返回空结果。后面的UNION把users表5个字段直接合并进商品查询结果。前端界面不会区分数据来自哪张表,只管渲染。于是你会在商品列表里看到一行行"商品"——名字是用户ID,描述是邮箱,价格是密码哈希。
用curl更直观:
curl "http://localhost:3000/api/products/search?q=DELIVERED%27%20UNION%20SELECT%20id%2C%20email%2C%20password%2C%20role%2C%20addressId%20FROM%20users--"
返回的JSON里,password字段躺着完整的用户凭证。
代码解剖:$queryRawUnsafe的危险承诺
漏洞核心在这里:
const sqlQuery = `
SELECT * FROM products
WHERE name LIKE '%${query}%'
OR description LIKE '%${query}%'
const products = await prisma.$queryRawUnsafe(sqlQuery);
注意那个Unsafe后缀。Prisma文档写得清楚:这个方法不转义、不参数化,把字符串原样发给数据库。开发者用了它,就等于亲手拆掉了ORM的防护墙。
更讽刺的是,Prisma提供了安全的替代方案——$queryRaw配合模板字符串标签函数:
const products = await prisma.$queryRaw`
SELECT * FROM products
WHERE name LIKE ${`%${query}%`}
同样能实现模糊搜索,但用户输入会被自动转义,注入攻击无从下手。区别只在于:有没有那个Unsafe后缀,以及有没有把变量直接嵌进模板字符串。
修复方案:一行代码的差距
最安全的改法是用参数化查询。如果业务需要动态表名或列名(本例不需要),可以配合查询构建器,但绝不要把用户输入直接拼接进SQL字符串。
另外,生产环境建议启用Prisma的查询日志审计,定期检查有没有代码调用了$queryRawUnsafe。这个函数名本身就是警示——除非万不得已,别碰它。
SQL注入在2024年依然稳居OWASP Top 10,不是因为技术多新,而是因为总有人图省事。ORM不是万能药,它提供的安全边界,需要开发者主动待在边界内。一旦为了"灵活"而绕过防护,代价可能是整库数据泄露。
那个Twitter截图里的电商,后来发公告说"已修复"。但没人知道,在修复之前,有多少人的密码已经被拖走。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.