在 PHP 面试中,Web 安全几乎是绕不开的核心考点。SQL 注入、XSS、CSRF 作为最常见的三类安全漏洞,不仅考察候选人对攻击原理的理解,更考验其在真实项目中的防御实践能力。本文将从攻击原理出发,逐一剖析这三类漏洞的防御方案,并给出可直接落地的 PHP 代码示例。
一、SQL 注入的防御
攻击原理
SQL 注入的本质是用户输入被拼接进 SQL 语句后,改变了原有语义。例如:
$sql = "SELECT * FROM users WHERE name = '$_GET[name]'";
当用户传入 ' OR '1'='1 时,查询条件恒真,攻击者即可绕过认证甚至拖库。
防御方案
1. 首选:预处理语句(Prepared Statements)
使用 PDO 或 MySQLi 的预处理功能,将 SQL 结构与数据分离,是防御 SQL 注入最根本的手段。
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8mb4', $user, $pass);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); // 使用真正的预处理
$stmt = $pdo->prepare('SELECT * FROM users WHERE name = :name AND status = :status');
$stmt->execute([':name' => $_GET['name'], ':status' => 1]);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
关键点:关闭 ATTR_EMULATE_PREPARES,避免 PDO 在客户端模拟预处理导致转义失效。
2. 输入验证与类型约束
对于明确类型的参数(如 ID),强制类型转换是最简单有效的防线:
$id = (int)($_GET['id'] ?? 0);
3. 最小权限原则
数据库账号不应使用 root,只授予业务所需的 SELECT/INSERT/UPDATE/DELETE 权限,禁止 DROP、FILE 等危险操作。
4. 避免拼接,禁止使用 mysql_* 系列函数
mysql_query 等旧扩展已在 PHP 7 中移除,但仍需警惕项目中遗留的字符串拼接写法。表名、字段名无法用预处理时,应使用白名单校验。
二、XSS 的防御
攻击原理
XSS(跨站脚本)是攻击者将恶意脚本注入页面,在其他用户浏览器中执行。常见于评论区、用户昵称、搜索回显等位置。分为存储型、反射型和 DOM 型三类。
防御方案
1. 输出转义(核心原则:永远不要信任用户输入)
在将数据输出到 HTML 上下文时,使用 htmlspecialchars 转义:
echo htmlspecialchars($userInput, ENT_QUOTES | ENT_HTML5, 'UTF-8');
注意必须指定 ENT_QUOTES 同时转义单双引号,并显式指定字符集,防止宽字节绕过。
2. 区分上下文进行转义
不同上下文需要不同的转义策略:
- HTML 内容:
htmlspecialchars - HTML 属性:同样使用
htmlspecialchars,属性值必须加引号 - JavaScript 变量:使用
json_encode($data, JSON_HEX_TAG | JSON_HEX_AMP) - URL 参数:使用
urlencode
3. 富文本场景使用 HTML Purifier
若业务允许用户提交富文本,不能简单转义,应使用白名单过滤库:
$purifier = new HTMLPurifier();
$clean = $purifier->purify($_POST['content']);
4. 设置 CSP 响应头
内容安全策略(CSP)作为纵深防御,可限制脚本来源:
header("Content-Security-Policy: default-src 'self'; script-src 'self'");
5. Cookie 设置 HttpOnly
防止脚本读取敏感 Cookie:
setcookie('session_id', $sid, ['httponly' => true, 'secure' => true, 'samesite' => 'Lax']);
三、CSRF 的防御
攻击原理
CSRF(跨站请求伪造)利用用户已登录的 Cookie,诱导其在不知情下向目标站点发起请求,如转账、改密等。攻击者无需获取 Cookie,只需构造请求即可。
防御方案
1. CSRF Token(主流方案)
在表单中嵌入一次性令牌,服务端校验:
session_start();
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
$token = $_SESSION['csrf_token'];
表单中:
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($token) ?>">
服务端校验:
if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'] ?? '')) {
http_response_code(403);
exit('CSRF token 校验失败');
}
使用 hash_equals 进行时间恒定比较,防止时序攻击。
2. SameSite Cookie 属性
将 Session Cookie 设置为 SameSite=Lax 或 Strict,可有效阻断跨站请求携带 Cookie:
session_set_cookie_params([
'samesite' => 'Lax',
'httponly' => true,
'secure' => true,
]);
3. 校验 Referer / Origin
对敏感操作校验请求来源:
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
if (!in_array($origin, ['https://example.com'], true)) {
exit('非法来源');
}
4. 关键操作二次验证
对于转账、修改密码等高风险操作,要求输入密码或短信验证码,从根本上降低 CSRF 危害。
四、面试答题要点总结
| 漏洞 | 核心防御 | 补充措施 |
|---|---|---|
| SQL 注入 | 预处理语句 + 参数绑定 | 最小权限、类型转换、白名单 |
| XSS | 输出转义(按上下文) | CSP、HttpOnly、HTML Purifier |
| CSRF | CSRF Token + SameSite | Referer 校验、二次验证 |
面试中回答此类问题,建议遵循“原理 → 危害 → 防御 → 代码示例”的结构,并强调纵深防御思想:没有单一方案能解决所有问题,需要多层防护协同。同时要体现对细节的把握,例如 PDO::ATTR_EMULATE_PREPARES 的关闭、htmlspecialchars 的字符集参数、hash_equals 的时序安全比较,这些细节往往是区分候选人水平的关键。
掌握这三类漏洞的防御,不仅是面试加分项,更是每一位 PHP 工程师写出安全代码的基本功。
未经允许不得转载:任鹏个人博客 » PHP 面试题:PHP 中 SQL 注入、XSS、CSRF 的防御方案

