|
|
这几年折腾老牌轻论坛程序 xiuno,最头疼的一件事就是补SQL注入的坑。xiuno胜在轻巧、结构清晰,但早期版本在输入过滤和数据库抽象层上的设计比较“敢”,给了开发者很大自由,也就意味着一不留神就会把注入窗口留给用户输入。我的经验是:别指望一把梭的“全局过滤”,要把防注入当作一整套
流程,从入口校验、参数白名单、到SQL层的预处理与最小权限控制,每一环都别偷懒。
先说入口。表单、QueryString、JSON 请求体,不要迷信“统一 addslashes”之类的老招。更稳的做法是对每个接口定义参数 schema:类型(int/string/array)、范围(min/max/enum)、长度上限以及必要性。拿帖子发布来说,uid 必须是正整数,title 限 80 字符且禁止控制字符,content 允许更长但要做 HTML 过滤(最好做白名单而不是黑名单)。这一步不是为了“过滤 SQL”,而是把脏数据挡在业务之外,顺便减少数据库压力。
再说到 SQL 生成。xiuno 常见写法是拼接字符串,这在开发期很爽,但等到复杂 where 条件时,出错率直线上升。建议改造为统一的数据库访问层,强制使用预处理语句和占位符,把“拼 SQL”的权力从业务代码手里收回来。即便底层还是 mysqli,也要封装 exec(query, params) 这样的函数,支持 ? 或 :name 占位。更进一步,给常用操作(插入、更新、批量插入、分页查询)各做一套安全 API,业务只传数据结构,不直接写片段。这样做的副作用是早期迁移会痛,但长远看能把 80% 的注入面消掉。
参数白名单是另一个关键点。很多人喜欢把用户传来的 sort、order、field 直接塞进 SQL,这是事故高发地。排序字段请做硬编码映射,比如只允许 'tid'、'create_date'、'views',传其他一律退回或回退默认;排序方向只接受 'asc'/'desc' 两个固定值。动态表名、动态列名能避免就避免,实在要动态也请通过映射表间接转换,不给用户任何自由拼装的余地。
批量操作和 IN 子句要小心。IN (?, ?, ?) 这种占位需要和参数长度一致,有的人偷懒直接拼字符串,风险就来了。可以在数据库层提供 buildInClause(values) 帮助器,保证每个元素都走占位,空数组时回退成 1=0,避免“IN ()”语法报错被临时字符串顶上。分页 offset/limit 同理,强制转 int 并设置最大上限,别让用户用超大 limit 拖垮库。
日志和报警不能省。把所有 SQL 执行前的“最终语句模板 + 参数”记录下来(注意脱敏),一旦发现异常字符频繁出现在参数里(比如注释符、关键字模式),可以触发速率限制或临时封禁。别做事后诸葛:线上的攻击流量往往具有节奏和重复性,早点抓到模式,后续修补会轻松很多。
权限和账号层面也能兜底。给数据库用户最小权限:只读接口用只读账号,写接口用写账号,绝不授予 DROP/ALTER 之类的 DDL;区分管理后台与前台的连接池,避免一处突破就全盘皆输。涉及财务、私信、邮箱等敏感表,再加一层服务端校验:即便业务判断通过,最终提交前再做一次与会话/权限的一致性检查。
老项目改造有个现实难点:存量的“拼接式”代码太多。我的做法是分层迁移:先把所有直连数据库的点集中到一处“适配层”,哪怕里面暂时还在拼接;随后逐个接口替换为预处理;最后收紧参数白名单与字段映射。每完成一批,配合回归测试与压测,确保性能不掉队。别一口吃成胖子,注入防护是和演进同频的工程活。
最后提醒一点:防注入不是等同于“过滤危险字符”。真正的解法是“让数据永远当数据,不让它当指令”。当你把类型校验、白名单、预处理、最小权限和审计串起来,xiuno 这类轻论坛也能跑得既安全又利索。剩下的就是纪律:新代码不破底线,旧代码逐步清债,别让省下的一行字符串拼接,变成一次全站事件。 |
|