PHP项目中的安全性问题 - Zanealancy博客

PHP项目中的安全性问题

随着业务复杂度的提升,在系统研发过程中,PHP应用程序代码量大,研发人员多,难免会出现疏漏。即使漏洞被修复,由于很多业务系统迭代速度快、升级频繁,人员经常变更,也会导致代码不一致,已修复的漏洞可能又出现在新代码中。同时在很多情况下同一台服务器会运行多个 Web系统,即使保证了自己研发的系统没有安全漏洞,但也无法避免系统被感染。

为什么 PHP 安全重要?

PHP 是全球最流行的服务器端脚本语言之一,广泛用于网站和 Web 应用。 若不注意安全,容易遭受如 SQL 注入、XSS、CSRF 等常见 Web 攻击。 安全漏洞可能导致用户数据泄露、网站被黑、服务器被控制,甚至法律责任。

PHP 项目中常见的安全风险

风险类型 简要说明
SQL 注入 用户输入未经处理直接拼接到 SQL 查询中,导致数据库被操控。
跨站脚本(XSS) 恶意脚本通过用户输入注入并执行于其他用户浏览器中。
跨站请求伪造(CSRF) 攻击者诱使用户在已登录状态下执行非意愿操作。
文件上传漏洞 允许上传可执行脚本(如 .php),导致远程代码执行。
会话劫持/固定 攻击者窃取或预测 Session ID,冒充合法用户。
敏感信息泄露 错误信息、配置文件暴露数据库密码、路径等敏感内容。
命令注入 通过用户输入执行系统命令,控制服务器。

基本安全最佳实践

  • 永远不要信任用户输入

    任何来自外部的数据(如 $_GET、$_POST、$_COOKIE、$_FILES、URL 参数、HTTP 头等)都可能被恶意构造。攻击者可利用这些输入执行注入、绕过逻辑、上传恶意文件等。 建议:

    1. 验证(Validation):检查输入是否符合预期格式(如邮箱、手机号、数字范围)。
    2. 过滤(Filtering):移除或替换非法字符(如使用 filter_var())。
    3. 转义(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 应用开发中至关重要。

  1. 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 被注入恶意代码,也无法修改核心代码或访问系统敏感文件。
  2. 数据库账户权限最小化 问题:使用 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;
  3. 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),确保输入严格过滤,并考虑使用容器隔离。
  4. 文件系统访问控制 PHP 脚本不应能访问整个服务器文件系统。

    • 使用 open_basedir 限制 PHP 只能访问指定目录:
    ; php.ini 或 .htaccess
    open_basedir = /var/www/html:/tmp
    此设置可防止通过 file_get_contents() 读取 /etc/passwd 等敏感文件。
  5. API 与后台管理权限分离

    • 普通用户接口 ≠ 管理员接口。
    • 实现基于角色的访问控制(RBAC):

      if (!currentUser()->hasRole('admin')) {
              http_response_code(403);
              die('Access denied');
      }
    • 敏感操作(如删除用户、导出数据)必须二次验证(密码、OTP)。

曾经不少人说PHP漏洞很多,需要打补丁,并非如此。PHP 本身不是不安全的,但不安全的编码习惯会导致严重后果。工作学习中需要特别注意编码安全习惯!