首页 / PHP 入门教程 / PHP 安全基础

PHP 入门教程

PHP 安全基础

本教程共 65 篇 · 第 42 篇 · 更新于 2026-07-24 · 约 5 分钟阅读

PHPPHP8安全SQL注入XSSCSRF密码哈希

42. PHP 安全基础

本节目标:了解 Web 开发中最常见的安全威胁,学会用 PHP 内置机制防御 SQL 注入、XSS、CSRF,掌握安全的密码存储方式。

安全不是功能做完后的”加分项”,而是贯穿开发全程的底线。本章讲解最常见、最危险的几种攻击及防御方法。

42.1 SQL 注入防护

SQL 注入是最危险的漏洞之一。攻击者通过构造恶意输入,篡改数据库查询。

危险的写法

<?php
$id = $_GET["id"] ?? "";

// 危险!用户可传入 id=1 OR 1=1 获取所有数据
$sql = "SELECT * FROM users WHERE id = {$id}";
$result = mysqli_query($conn, $sql);

安全的写法:预处理语句

<?php
// PDO 方式(推荐)
$pdo = new PDO("mysql:host=localhost;dbname=test", "user", "pass");

$stmt = $pdo->prepare("SELECT * FROM users WHERE id = :id");
$stmt->execute(["id" => $_GET["id"]]);
$user = $stmt->fetch();

// mysqli 方式
$stmt = $conn->prepare("SELECT * FROM users WHERE email = ?");
$stmt->bind_param("s", $_POST["email"]);
$stmt->execute();
Note

预处理语句把 SQL 结构和数据分开,数据库会把用户输入当作纯数据处理,从根本上杜绝 SQL 注入。

42.2 XSS(跨站脚本)防护

XSS 让攻击者把恶意脚本注入到你的页面,窃取用户 Cookie 或冒充用户操作。

危险的输出

<?php
$name = $_GET["name"] ?? "";

// 危险!用户传入 <script> 就会执行
echo "欢迎,{$name}!";

安全的输出

<?php
$name = $_GET["name"] ?? "";

// 转义后输出
echo "欢迎," . htmlspecialchars($name, ENT_QUOTES, "UTF-8") . "!";

// 或用 filter 净化
echo "欢迎," . filter_var($name, FILTER_SANITIZE_SPECIAL_CHARS) . "!";
Tip

所有来自用户、数据库、API 的数据输出到 HTML 前,都必须转义。养成”输出即转义”的习惯。

不同场景的转义

<?php
// HTML 内容
$html = htmlspecialchars($text, ENT_QUOTES, "UTF-8");

// HTML 属性(额外加引号保护)
$attr = htmlspecialchars($text, ENT_QUOTES);

// JavaScript
$js = json_encode($text, JSON_HEX_TAG | JSON_HEX_AMP);

// URL 参数
$url = urlencode($text);

42.3 CSRF(跨站请求伪造)防护

CSRF 让攻击者诱导已登录用户访问恶意链接,以用户身份执行非自愿操作(如转账、改密码)。

防御原理

确保请求来自你自己的页面,而非第三方网站:

  1. 生成随机 Token 存入 Session
  2. 表单中嵌入该 Token
  3. 提交时验证 Token 是否匹配
<?php
session_start();

// 生成 CSRF Token(首次访问时)
if (empty($_SESSION["csrf_token"])) {
    $_SESSION["csrf_token"] = bin2hex(random_bytes(32));
}
?>

<!-- 表单中嵌入 Token -->
<form method="post" action="transfer.php">
    <input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
    收款人:<input type="text" name="to">
    金额:<input type="number" name="amount">
    <input type="submit" value="转账">
</form>
<?php
session_start();

// 验证 Token
$token = $_POST["csrf_token"] ?? "";
if (!hash_equals($_SESSION["csrf_token"] ?? "", $token)) {
    die("CSRF 验证失败");
}

// 继续处理转账...
Note

hash_equals() 是防时序攻击的安全比较函数,不要用 === 直接比较字符串。

42.4 安全的密码存储

永远不要用 MD5、SHA1 或明文存储密码。

正确的做法:password_hash()(详见第 25 章)

<?php
$password = "user123456";

// 注册时生成哈希
$hash = password_hash($password, PASSWORD_DEFAULT);
// 存入数据库

// 登录时验证
if (password_verify($inputPassword, $hashFromDb)) {
    echo "密码正确";

    // 检查是否需要重新哈希(PHP 升级了默认算法时)
    if (password_needs_rehash($hashFromDb, PASSWORD_DEFAULT)) {
        $newHash = password_hash($inputPassword, PASSWORD_DEFAULT);
        // 更新数据库中的哈希值
    }
} else {
    echo "密码错误";
}
Tip

PASSWORD_DEFAULT 在 PHP 8.x 使用 bcrypt,成本因子为 10。它会随 PHP 版本自动升级,始终使用当前最安全的算法。

42.5 其他安全建议

文件上传安全

<?php
// 1. 验证 MIME 类型(用 finfo,不用 $_FILES['type'])
// 2. 重命名文件,不用原始文件名
// 3. 限制扩展名白名单
// 4. 上传目录不要设为可执行
// 5. 图片建议二次压缩生成,清除可能的恶意代码

Session 安全

<?php
ini_set("session.cookie_httponly", "1");       // 禁止 JS 读取
session_set_cookie_params(["samesite" => "Strict"]); // 防 CSRF
session_regenerate_id(true);                    // 登录后更换 ID

错误信息控制

<?php
// 生产环境不要暴露详细错误
ini_set("display_errors", "0");
ini_set("log_errors", "1");

来源:参考了 GitHub「php-the-right-way」Security 章节、runoob「PHP 安全」等,改写后所得。