SQL注入防御必须用参数化查询,上传文件需内存校验+服务端重命名+Web服务器层拦截。

SQL注入防御:别只靠字符串拼接加引号
用 mysql_real_escape_string 或手动加单引号包裹用户输入,本质是补漏,不是防御。它对数字型参数无效(比如 id=1 OR 1=1),也挡不住宽字节、编码绕过、或预编译被绕开的场景。真正可靠的方式只有参数化查询——把数据和 SQL 结构彻底分开。
- PHP 中必须用
PDO::prepare()+bindValue(),禁用query()直接拼接 - Python 的
sqlite3.execute()、psycopg2.cursor.execute()都支持占位符,用%s或$1,不要用.format()或f"" - Node.js 的
pg.query()、mysql2.execute()同理,传参数组或对象,不拼字符串 - 即使是
ORDER BY或IN这类无法参数化的字段,也要白名单校验:if sort_field not in ['created_at', 'score']:就直接拒掉
上传文件过滤:后缀、MIME、内容三者缺一不可
仅检查 $_FILES['file']['name'] 的后缀(如 .jpg)等于没防。攻击者改个 shell.php.jpg,再配合 Nginx 配置缺陷就能执行;只读 $_FILES['file']['type'] 更危险——那是浏览器传的,Content-Type: image/jpeg 伪造零成本。
- 服务端重命名文件,去掉原始名,用随机字符串 + 白名单扩展名(如
uniqid().'.png') - 用
finfo_open()(PHP)或python-magic(Python)检测真实文件头,image/png不等于“是 PNG” - 对图片类文件,可调用
getimagesize()或PIL.Image.open().verify()做二次解析校验 - 上传目录禁止执行权限,Nginx/Apache 配置中明确
location ~ .php$ { deny all; }
为什么「先校验再保存」会出问题
常见写法是:收到文件 → 检查后缀 → 检查 MIME → 保存到临时目录 → 再移动到正式路径。但中间这一步临时保存,如果路径可控或目录可列,就可能被提前访问。更糟的是,某些语言(如旧版 PHP)在 move_uploaded_file() 前,文件已写入临时区且能被其他进程读取。
- 所有校验必须在内存中完成(如读取前 4KB 判断 magic bytes),避免落盘后再检
- 不要依赖
$_FILES['file']['tmp_name']的路径做任何逻辑判断,它只是临时句柄 - 如果必须落地,用
sys_get_temp_dir()+tempnam()生成不可猜路径,并设好 umask(0600)
别让 .htaccess 或 web.config 成为摆设
有人以为在上传目录放个 .htaccess 禁止 PHP 执行就安全了,但 Apache 可能没启用 AllowOverride All,或者用了 FastCGI 模式导致规则不生效;IIS 的 web.config 同样可能被忽略。
- 检查服务器实际生效的配置:PHP 中用
apache_get_modules()看是否加载mod_rewrite,或直接 curl 测试xxx.php.jpg是否返回 500 而非 200 - 更稳妥的是从 Web Server 层直接拦截:Nginx 用
location ^~ /uploads/ { location ~ .(php|phtml|phar)$ { return 403; } } - Windows 主机注意短文件名(8.3 格式)绕过,比如
shell.php可能被解析为SHELL~1.PHP,需额外禁用
上传路径和 SQL 参数看似独立,但一旦其中一个环节松动,攻击链就通了。最常被跳过的其实是「校验是否真在内存里做」和「Web Server 层是否真的拦截了执行」——这两步不验证,前面全白忙。

















