数据导出功能需同步防御SQL注入与公式注入——SQL层须全程预处理+白名单校验动态参数,导出层须对单元格值前置单引号并禁用自动类型推断,响应头与文件名须严格过滤。

数据导出功能是SQL注入和公式注入的高危交汇点——用户控制的字段名、排序参数、筛选条件可能被拼进SQL,而导出的Excel/CSV内容又可能被Excel主动解析为公式(如=cmd|' /C calc'!A0)。单防SQL注入或单防公式注入都不够,必须同步切断两条攻击链。
导出SQL查询必须全程使用预处理语句
导出接口常因“动态字段”“动态WHERE”误用字符串拼接,这是SQL注入的入口。哪怕只拼一个ORDER BY字段,也会被绕过。
- 禁止任何形式的用户输入直接进入SQL字符串,包括
$_GET['sort']、$_POST['fields']、$_GET['status'] - 字段名、表名、排序方向等无法参数化的部分,必须走白名单校验:
$allowed_sorts = ['name', 'created_at', 'status']; $sort = in_array($_GET['sort'], $allowed_sorts) ? $_GET['sort'] : 'id'; - LIKE查询中的通配符必须在PHP层添加,不能拼进占位符右侧:
$search = '%' . $pdo->quote($_POST['q']) . '%'; $stmt->execute([$search]);(注意:这里quote()仅作示例,实际应优先用参数化) - 连接字符集必须显式指定为
utf8mb4,避免宽字节注入绕过:new PDO("mysql:host=localhost;dbname=test;charset=utf8mb4", ...)
导出内容必须对单元格值做公式前缀转义
Excel会把以=、+、-、@开头的单元格内容自动识别为公式执行,攻击者可借此触发任意命令。这不是前端问题,必须在服务端写入CSV/Excel时处理。
- 对每个待导出的字符串值,前置单引号
':'=cmd|' /C calc'!A0→'=cmd|' /C calc'!A0(Excel将其视为文本) - 若业务允许显示公式,需双重转义:先加
',再将=替换为''=(两个单引号开头) - 使用
fputcsv()导出CSV时,PHP默认会加双引号包裹字段,但不阻止公式执行;必须手动对值做str_replace(['=', '+', '-', '@'], ["'=", "'+", "'-", "'@"], $value) - 用PhpSpreadsheet等库生成Excel时,调用
setCellValueExplicit()并指定DataType::TYPE_STRING,禁用自动类型推断
导出参数与响应头必须严格分离
攻击者常利用导出功能的响应头污染实现二次攻击,比如在Content-Disposition中注入换行符+HTTP头,或在文件名里塞恶意公式。
- 文件名必须经过
basename()+ 白名单字符过滤(只留字母、数字、下划线、短横线),禁用rawurlencode()以外的编码方式 -
Content-Type固定为text/csv或application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,绝不根据用户传参动态设置 - 禁用
X-Content-Type-Options: nosniff以外的非常规响应头,防止利用MIME嗅探绕过 - 导出接口禁止返回HTML、JSON等非二进制内容,响应体必须是纯CSV/Excel字节流
最易被忽略的是:公式注入防护不能依赖前端JavaScript,因为导出请求可被curl直发;SQL注入防护也不能只靠ORM的“自动参数化”,因为手写原生SQL或动态构建查询时极易漏掉某个ORDER BY字段。两条链路必须各自独立加固,且在同一个请求生命周期内同时生效。











