直接用fgetcsv()拼sql会出事,因其仅按分隔符切字段而不校验内容,易致公式注入或sql注入;须在绑定参数前对每字段trim、拦截公式前缀、类型强转并过滤。

为什么直接用 fgetcsv() 读取后就拼SQL会出事
因为 fgetcsv() 只负责按分隔符切字段,不关心内容是不是恶意字符串。比如某行 CSV 是 "admin","12345','=cmd|\' /C notepad\'!A0",第三列看着像普通文本,但导出再用 Excel 打开时可能触发公式执行;更危险的是,如果后续代码把它拼进 SQL 里:"INSERT INTO users (name, pass, note) VALUES ('$name', '$pass', '$note')",攻击者就能闭合引号、注入任意语句。
校验必须在绑定参数前做,且不能只看长度
常见误区是只校验 count($row) === 3 就认为安全——这防不住字段内藏 SQL 片段。真正要做的,是在调用 $stmt->execute() 前,对每个字段做三件事:
使用 qbo-mileage CLI 及用户凭证,从 Airtable、Outlook 或 Google Calendar 记录生成 QuickBooks Online 里程 CSV 文件。
- 用
trim()去首尾空白,避免隐藏空格干扰判断 - 对疑似公式开头的字符(
=、+、-、@)做前缀拦截:若ltrim($field)[0]是其中之一,直接拒绝该行或替换为"[BLOCKED]" - 数字字段强制用
(int)或filter_var($field, FILTER_VALIDATE_INT)转换,失败则设默认值(如0),不传原始字符串
empty() 不等于“安全”,得配合类型感知清洗
empty($row[2]) 返回 true 时,可能是空字符串、"0"、"false",甚至 "=NOW()"(因为 = 开头被 Excel 当公式,但 PHP 的 empty() 仍判为非空)。所以不能只靠 empty() 决定是否跳过。正确做法是:
- 字符串字段:用
preg_match('/^[=+\-@]/u', trim($field))检查公式前缀 - 数字字段:用
is_numeric($field) && $field == (string)(int)$field判断是否纯整数(防"123abc") - 邮箱/URL 类字段:必须走
filter_var($field, FILTER_VALIDATE_EMAIL)等专用过滤器
预处理语句不是万能的,BOM 和编码错位会让校验失效
如果 CSV 文件带 UTF-8 BOM(\xEF\xBB\xBF),fgetcsv() 读出的第一列可能变成 "\xEF\xBB\xBFname",导致 trim() 后仍残留不可见字符,进而让正则匹配失败。解决方案是:
- 打开文件前先检测并剥离 BOM:
$content = file_get_contents($file); $content = preg_replace('/^\xEF\xBB\xBF/', '', $content); file_put_contents($file, $content); - 或改用流式处理:
$fp = fopen($file, 'r'); stream_filter_append($fp, 'convert.iconv.UTF-8/UTF-8//IGNORE'); - 数据库连接必须显式设
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4",否则中文字段里的单引号可能被截断,破坏参数化边界
$stmt->execute() 已经用上了未清洗的原始值。最保险的做法是:每行 fgetcsv() 后立刻进一个 sanitize_csv_row() 函数,返回数组,之后只操作这个返回值。










