防住sql注入和数据泄露的关键在于参数绑定全覆盖、输出全转义、敏感字段不落地;doctrine预处理+twig默认转义+dto封装可拦截95%风险,且需禁用pdo模拟预处理、最小权限数据库账号、路由参数约束及生产环境错误屏蔽。

防住 SQL 注入和数据泄露,关键不在“用没用 Symfony”,而在于参数绑定是否全覆盖、输出是否全转义、敏感字段是否不落地。Doctrine 的预处理机制 + Twig 默认转义 + DTO 封装,这三者配合到位,95% 的风险就已拦下。
参数绑定必须覆盖所有查询路径
只要 SQL 字符串里出现用户输入,就必须走参数化——DQL、Query Builder、原生 SQL 全部适用,没有例外。
- DQL 中用
:name或?1占位,再调setParameter('name', $input) - Query Builder 写
$qb->where('u.email = :email'),链式传参->setParameter('email', $request->query->get('email')) - 原生 SQL 必须用
executeStatement($sql, $params)或executeQuery($sql)->setParameters($params);绝不能拼字符串:"WHERE id = " . $_GET['id'] - 常见高危写法:把路由参数直接插进 DQL 字符串、用
sprintf()拼条件、未经校验就把$_GET['sort']当字段名用
无法参数化的部分必须白名单兜底
表名、字段名、ORDER BY 方向、IN 列表这些语法要素,数据库不支持占位符,只能靠强校验补位。
- 字段/表名只允许来自预定义数组:
$allowedFields = ['name', 'email', 'createdAt']; in_array($input, $allowedFields, true) - 排序方向统一转小写判断:
$dir = strtolower($request->query->get('dir', 'asc')) === 'desc' ? 'DESC' : 'ASC' -
IN列表先整型过滤:array_map('intval', $ids),再交给 Doctrine:setParameter('ids', $ids, Connection::PARAM_INT_ARRAY) - 禁用 PDO 模拟预处理:
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false),但要确认驱动真实支持
敏感数据不出库、不缓存、不直传
脱敏不是“前端藏一藏”,而是从查询结果那一刻起就切断原始值暴露路径。
- 不把 Entity 直接传给模板或 API 响应,改用 DTO 封装,在构造时完成手机号、身份证号等字段的掩码处理
- 数据库层禁明文:密码必须 bcrypt/Argon2 编码;银行卡号、证件号考虑加密存储,而非依赖查询时处理
- 禁用敏感实体的二级缓存,若需缓存,只存脱敏后的 DTO 数组,并设短 TTL
- Twig 中避免在模板里写
{{ user.phone|substr(0,3) ~ '****' ~ user.phone|substr(-4) }},应统一由 PHP 层提供getMaskedPhone()方法或全局过滤器
其他加固层不可省略
参数绑定是核心防线,但单点防护容易被绕过,需配合配置与流程协同。
- 数据库账号启用最小权限原则:禁用
DROP、CREATE、LOAD_FILE等高危权限 - 路由参数加约束,如
{id}限制必须为数字,从入口过滤非法字符 - 生产环境关闭详细错误提示,避免泄露表结构或 SQL 片段
- 命令行工具中所有
$input->getArgument()和getOption()都要白名单校验,禁用exec类函数拼接调用











