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 让攻击者诱导已登录用户访问恶意链接,以用户身份执行非自愿操作(如转账、改密码)。
防御原理
确保请求来自你自己的页面,而非第三方网站:
- 生成随机 Token 存入 Session
- 表单中嵌入该 Token
- 提交时验证 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 安全」等,改写后所得。