PHP项目上线前的安全检测不是走个过场,很多被入侵的站点问题都出在最后一步没做系统排查。与其等上线后被挂马、被拖库、被刷接口,不如在部署到生产环境之前把关键风险点全部过一遍。下面直接按检测流程展开,从代码层到服务器层,每个环节都给出可操作的判断标准和修复思路。
1. 先查依赖与框架版本,别让已知漏洞跟着上线PHP项目最容易被忽略的是composer依赖里的已知漏洞。上线前先执行composer audit,这个命令会直接对照Packagist的安全公告数据库,列出存在已知漏洞的包及版本。如果项目还在用ThinkPHP 5.0.x、Laravel 5.x这类老版本,或者某个第三方库已经两年没更新,就要重点检查。修复方式很简单:升级到安全版本,或者用composer.lock锁定补丁版本。不要手动去改vendor目录里的代码,那样下次composer install会覆盖掉。
另外检查PHP自身版本。PHP 7.4以下官方已停止安全更新,如果生产环境还在跑PHP 5.6或7.0,上线前必须升级或加一层WAF做临时缓解。用php -v确认版本,再对照php.net/supported-versions查看支持状态。
2. 关闭所有调试输出和错误回显很多PHP项目在开发环境为了方便调试,会把display_errors设为On,错误报告级别设为E_ALL。上线前必须检查php.ini或项目配置文件,确保display_errors=Off,log_errors=On。错误信息里往往包含数据库表名、文件绝对路径、堆栈调用信息,这些对攻击者来说都是非常有价值的侦察数据。错误日志要写到单独的文件,不要输出到浏览器。
框架层面也要检查,比如Laravel的.env里APP_DEBUG必须为false,ThinkPHP的app_debug要关闭。上线前用curl请求一个不存在的路由,看返回的是不是简洁的404页面而不是带调试信息的报错页。
3. 数据库连接与权限最小化检查config目录下的数据库配置文件,确认生产环境用的不是root账号,并且数据库用户只授予了SELECT、INSERT、UPDATE、DELETE权限,没有GRANT、DROP、ALTER等管理权限。很多项目图省事,开发和生产用同一套账号,一旦SQL注入成功,攻击者就能直接读mysql.user表或者写文件。
数据库密码不要硬编码在代码里,要用环境变量或配置文件管理。上线前检查代码仓库里有没有提交过.env、config.php这类包含真实密码的文件。如果已经提交过,光删除文件不够,要改密码,因为git历史里还能翻到。
4. SQL注入检测:重点看拼接和未过滤的输入不要相信任何来自用户的输入。上线前用grep搜索代码里的SQL拼接模式,比如"select * from users where id=" . $_GET['id'],或者"where name = '$name'"这类写法。即使用了框架的查询构造器,也要检查有没有直接用DB::raw()或whereRaw()拼接用户输入。
修复方案统一用PDO预处理语句或框架自带的参数绑定。下面是一个正确示例:
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email');
$stmt->execute(['email' => $email]);
$user = $stmt->fetch();
如果项目里存在大量原生SQL,上线前至少要把所有接收GET、POST参数的SQL语句过一遍,优先修复登录、搜索、列表筛选这类直接暴露给用户的接口。
5. XSS检测:输出转义比输入过滤更重要PHP项目里XSS漏洞的根源通常是输出时没有转义。检查模板里所有用echo输出的变量,特别是用户提交过的内容,比如用户名、评论、搜索关键词。原生PHP里用htmlspecialchars($str, ENT_QUOTES, 'UTF-8'),框架里用对应的转义函数,比如Laravel的{{ }}会自动转义,ThinkPHP模板里用{$var|htmlspecialchars}。
同时检查有没有用html_entity_decode或者把用户输入直接输出到JavaScript上下文里的情况。对于富文本内容,要用白名单过滤库,比如HTMLPurifier,不要用正则去过滤HTML标签,正则处理HTML非常不可靠。
6. 文件上传漏洞:类型、大小、存储位置三重校验文件上传功能是PHP项目的高危区域。上线前检查上传处理代码,确认做了以下验证:文件MIME类型用finfo_file检测真实类型,而不是只信$_FILES['file']['type'];文件扩展名用白名单,只允许jpg、png、pdf这类业务需要的类型;文件名要重新生成,不能用用户提供的原始文件名拼路径;上传目录要放在Web根目录之外,或者配置为不执行PHP。
如果上传目录在public下,攻击者上传一个shell.php就能直接执行。正确做法是把上传目录放在项目根目录之外的storage目录,通过PHP脚本读取输出,或者在上传目录放一个.htaccess(Apache环境)禁用PHP解析:
php_flag engine off
Nginx环境则要在配置里对上传目录单独设置location,不交给PHP处理。
7. 会话与Cookie安全配置检查session配置,确保session.cookie_httponly=1,session.cookie_secure=1(如果全站HTTPS),session.use_strict_mode=1。Cookie的SameSite属性建议设为Lax或Strict,防止CSRF。PHP 7.3以上可以直接在php.ini里设置session.cookie_samesite=Lax。
另外检查登录逻辑里有没有在用户登录成功后重新生成session_id,防止会话固定攻击。代码里应该有session_regenerate_id(true)或框架对应的重新生成方法。退出登录时要彻底销毁session,不能只清掉几个session变量。
8. CSRF防护覆盖所有状态变更请求检查所有POST、PUT、DELETE请求是否都带了CSRF token。框架一般有内置支持,Laravel的@csrf指令、ThinkPHP的token验证器。但要注意检查AJAX请求有没有遗漏,特别是用原生fetch或jQuery写的请求,需要在header里带上X-CSRF-TOKEN。上线前用浏览器开发者工具把所有表单和AJAX请求过一遍,确认没有哪个状态变更操作是无token的。
9. 敏感文件和目录权限排查项目根目录下的.git、.svn、.env、composer.json、package.json等文件不能通过Web访问。上线前用curl测试这些路径,比如curl -I https://你的域名/.git/config,如果返回200就说明存在信息泄露。Nginx和Apache都要配置禁止访问隐藏文件。Apache可以在.htaccess里加:
RewriteRule (^\.|/\.) - [F]
Nginx则在server块里加:
location ~ /\. {
deny all;
}
同时检查storage、runtime、cache等可写目录的权限,不要设置成777。Web服务器用户只需要对这些目录有写权限,其他目录保持只读。
10. 接口限流与暴力破解防护登录、注册、短信验证码、密码找回这些接口必须有频率限制。上线前检查有没有实现限流逻辑,比如基于IP或用户名的失败次数记录。如果项目没有现成的限流组件,可以用Redis实现一个简单的计数器:同一IP在5分钟内失败超过5次就锁定15分钟。不要只在前端做倒计时,后端必须强制校验。
同时检查验证码机制是否有效,验证码不能只在前端验证,后端要校验并销毁,防止重放。
11. 命令执行与文件包含风险排查用grep搜索代码里的exec、shell_exec、system、passthru、popen、proc_open等函数,确认参数里没有用户可控的输入。如果业务确实需要执行系统命令,要用escapeshellarg()处理参数,并且命令本身要写死,不能拼接用户输入。
文件包含方面,检查include、require、include_once、require_once里的路径是否可能被用户控制。如果存在动态包含,要用白名单限制可包含的文件名,禁止包含远程URL。
12. 服务端HTTPS与安全响应头配置上线前确认全站强制HTTPS,HTTP请求301跳转到HTTPS。检查证书有效期和证书链完整性。然后在Web服务器配置里加上安全响应头:X-Content-Type-Options: nosniff,X-Frame-Options: DENY或SAMEORIGIN,Content-Security-Policy根据业务情况配置,至少限制script-src和object-src。PHP代码里也可以用header()函数设置,但更推荐在Web服务器层统一配置。
检查是否泄露服务器版本信息,Nginx要关掉server_tokens,Apache要设置ServerTokens Prod,PHP要设置expose_php=Off。
13. 日志与监控就位后再上线安全检测的最后一步是确认日志记录完整。PHP错误日志、Web服务器访问日志、数据库慢查询日志都要开启。关键业务操作,比如登录、注册、支付、后台管理操作,要有单独的审计日志。上线后一旦出现异常,没有日志就无法定位问题。
同时检查有没有配置基本的监控告警,比如文件被篡改、进程异常退出、CPU长时间占满。这些监控不一定要很复杂,简单的脚本定时检查文件hash变化就能发现webshell写入。
上线前的安全检测不是一次性的任务,而是一套可以重复执行的流程。把上述检查项整理成清单,每次发版前逐项过一遍,比上线后被动应急要有效得多。安全问题的修复成本在开发阶段最低,其次是上线前,一旦到了生产环境被攻击者利用,成本会成倍增加。
