PHP项目中的安全性问题
随着业务复杂度的提升,在系统研发过程中,PHP应用程序代码量大,研发人员多,难免会出现疏漏。即使漏洞被修复,由于很多业务系统迭代速度快、升级频繁,人员经常变更,也会导致代码不一致,已修复的漏洞可能又出现在新代码中。同时在很多情况下同一台服务器会运行多个 Web系统,即使保证了自己研发的系统没有安全漏洞,但也无法避免系统被感染。
为什么 PHP 安全重要?
PHP 是全球最流行的服务器端脚本语言之一,广泛用于网站和 Web 应用。 若不注意安全,容易遭受如 SQL 注入、XSS、CSRF 等常见 Web 攻击。 安全漏洞可能导致用户数据泄露、网站被黑、服务器被控制,甚至法律责任。
PHP 项目中常见的安全风险
| 风险类型 | 简要说明 |
|---|---|
| SQL 注入 | 用户输入未经处理直接拼接到 SQL 查询中,导致数据库被操控。 |
| 跨站脚本(XSS) | 恶意脚本通过用户输入注入并执行于其他用户浏览器中。 |
| 跨站请求伪造(CSRF) | 攻击者诱使用户在已登录状态下执行非意愿操作。 |
| 文件上传漏洞 | 允许上传可执行脚本(如 .php),导致远程代码执行。 |
| 会话劫持/固定 | 攻击者窃取或预测 Session ID,冒充合法用户。 |
| 敏感信息泄露 | 错误信息、配置文件暴露数据库密码、路径等敏感内容。 |
| 命令注入 | 通过用户输入执行系统命令,控制服务器。 |
基本安全最佳实践
-
永远不要信任用户输入
任何来自外部的数据(如 $_GET、$_POST、$_COOKIE、$_FILES、URL 参数、HTTP 头等)都可能被恶意构造。攻击者可利用这些输入执行注入、绕过逻辑、上传恶意文件等。 建议:
- 验证(Validation):检查输入是否符合预期格式(如邮箱、手机号、数字范围)。
- 过滤(Filtering):移除或替换非法字符(如使用 filter_var())。
- 转义(Escaping):在输出到不同上下文(SQL、HTML、Shell、JSON)前进行编码。
-
使用预处理语句防 SQL 注入
SQL 注入发生在将用户输入直接拼接到 SQL 查询字符串中。预处理语句(Prepared Statements)将 SQL 结构与数据分离,数据库引擎不会把参数当作 SQL 代码执行。 推荐方式:PDO 或 MySQLi(面向对象)
-
输出时转义 HTML 内容
跨站脚本(XSS)攻击通过在页面中注入
<script>、<img onerror=...>等标签,在用户浏览器中执行恶意 JavaScript。 建议:使用 htmlspecialchars() 防止 XSS。htmlspecialchars($user_input, ENT_QUOTES | ENT_HTML5, 'UTF-8');- ENT_QUOTES:转义单引号 ' 和双引号 "(对属性值很重要)
- ENT_HTML5:使用 HTML5 规范
- 指定编码为 UTF-8 避免乱码
输出上下文不同,转义方式也不同:
HTML 文本:htmlspecialchars( HTML 属性值:同上 + 引号包裹 JavaScript 字符串:使用 json_encode() URL 参数:urlencode() -
启用 HTTPS
HTTP 是明文传输,攻击者可通过中间人(MITM)窃听 Cookie、密码、会话 ID。HTTPS 使用 TLS/SSL 加密通信,防止窃听和篡改。 低成本方式从 Let’s Encrypt 获取免费证书(如 Certbot),更高要求的就购买商用证书
-
安全处理会话
设置安全 Cookie 属性(HttpOnly、Secure、SameSite),登录后更换 Session ID。 安全配置:
// 启动前设置 ini_set('session.cookie_httponly', 1); // JS 无法读取 Cookie ini_set('session.cookie_secure', 1); // 仅 HTTPS 传输(生产环境) ini_set('session.cookie_samesite', 'Strict'); // 防 CSRF ini_set('session.use_strict_mode', 1); // 禁止未初始化的 Session ID session_start(); // 登录成功后更换 Session ID session_regenerate_id(true); // 删除旧会话文件其他建议:
设置合理的会话超时时间 用户登出时调用 session_destroy() 不在 URL 中传递 Session ID(禁用 session.use_trans_sid)
-
限制文件上传
用户上传 .php、.phtml 等可执行文件,通过访问触发远程代码执行(RCE)。 白名单扩展名(不要黑名单,黑名单很容易绕过
(只要不存在黑名单里的任意后缀都能通过)!):$allowed = ['jpg', 'png', 'gif']; $ext = strtolower(pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION)); if (!in_array($ext, $allowed)) die('Invalid file type'); 不要依赖 $_FILES['type'] —— 客户端可伪造。检查 MIME 类型(但可伪造,需配合其他检查):
$finfo = finfo_open(FILEINFO_MIME_TYPE); $mime = finfo_file($finfo, $_FILES['file']['tmp_name']); if ($mime !== 'image/jpeg') { /* reject */ } -
关闭错误显示(生产环境)
PHP 错误信息(如数据库连接失败、文件路径)会暴露服务器内部结构,帮助攻击者定位漏洞。 在 php.ini 中设置:
display_errors = Off log_errors = On error_log = /var/log/php_errors.log自定义错误处理器(set_error_handler())可统一记录或展示友好错误页。 确保 Web 服务器(如 Nginx/Apache)也不暴露版本号或目录列表。
-
权限最小化原则
是信息安全中的核心原则之一,尤其在 PHP 项目和 Web 应用开发中至关重要。
-
Web 服务器与 PHP 进程的运行用户权限 问题:如果 Apache/Nginx + PHP-FPM 以 root 用户运行,一旦被入侵,攻击者可完全控制系统。 正确做法:
- 使用低权限专用用户(如 www-data、nginx、php-fpm)运行 Web 服务。
- 确保该用户不能登录 shell、无 sudo 权限。
- 文件目录权限设置为仅 Web 用户可读/写必要目录(如 uploads/),其他文件设为只读。
# 示例:设置 uploads 目录仅 www-data 可写 chown -R www-data:www-data /var/www/html/uploads chmod -R 755 /var/www/html # 其他目录只读 chmod -R 775 /var/www/html/uploads # 上传目录可写 ✅ 好处:即使 PHP 被注入恶意代码,也无法修改核心代码或访问系统敏感文件。 -
数据库账户权限最小化 问题:使用 root 或具有 DROP、GRANT 权限的数据库账号连接应用。 正确做法:
- 为每个 PHP 应用创建独立数据库用户。
- 仅授予所需权限(通常只需 SELECT, INSERT, UPDATE, DELETE)。
- 禁止 DROP, ALTER, CREATE USER, FILE, SHUTDOWN 等高危权限。
创建最小权限用户(MySQL 示例) CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'strong_password'; GRANT SELECT, INSERT, UPDATE, DELETE ON myapp_db.* TO 'app_user'@'localhost'; FLUSH PRIVILEGES; -
PHP 函数与扩展的限制 问题:PHP 默认允许执行系统命令(如 exec, shell_exec, system),可能被用于反弹 shell。 正确做法:
-
在 php.ini 中禁用危险函数(尤其在共享主机或不可信环境中):
disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source - 若确实需要某些功能(如 exec),确保输入严格过滤,并考虑使用容器隔离。
-
-
文件系统访问控制 PHP 脚本不应能访问整个服务器文件系统。
- 使用 open_basedir 限制 PHP 只能访问指定目录:
; php.ini 或 .htaccess open_basedir = /var/www/html:/tmp 此设置可防止通过 file_get_contents() 读取 /etc/passwd 等敏感文件。 -
API 与后台管理权限分离
- 普通用户接口 ≠ 管理员接口。
-
实现基于角色的访问控制(RBAC):
if (!currentUser()->hasRole('admin')) { http_response_code(403); die('Access denied'); } - 敏感操作(如删除用户、导出数据)必须二次验证(密码、OTP)。
曾经不少人说PHP漏洞很多,需要打补丁,并非如此。PHP 本身不是不安全的,但不安全的编码习惯会导致严重后果。工作学习中需要特别注意编码安全习惯!