PHP 安全加固实战:SQL 注入、XSS、CSRF 与 OWASP 全清单

系统覆盖 PHP Web 安全:SQL 注入与预处理、XSS 输出转义、CSRF 令牌、会话固定与劫持、文件上传漏洞、SSRF、Sensitive Data 泄露、Laravel 内置防护与 OWASP Top 10 对照。

引言

安全不是「上线前补一遍」,而是贯穿每个输入、每处输出的工程习惯。PHP 应用最常见的五大致命漏洞——SQL 注入、XSS、CSRF、文件上传、会话劫持——各有明确解法。本文按攻击面逐条给「攻击方式 → 正确防护 → Laravel 内置支持」,并对照 OWASP Top 10,让你从「会写功能」进阶到「敢上生产」。

前置:/php-mysql-database/(预处理防注入)、/php-laravel-internals/(中间件)。


目录


1. 攻击面地图与 OWASP Top 10

攻击面 = 一切「不可信输入」的入口:

浏览器输入(GET/POST/Cookie)→ 应用
文件上传 / 导入 → 存储
第三方 API / Webhook → 应用
数据库 / 缓存 → 应用

OWASP Top 10(2021)对照本专题:

OWASP 项本专题章节
A1 访问控制失效8(授权检查)
A3 注入2(SQL 注入)
A3 敏感数据暴露8
A4 不安全设计6/7(上传/SSRF)
A7 身份认证失效5(会话)
A8 软件和数据完整性失效依赖锁定
A9 日志与监控自检(留痕)

心智:先画攻击面,再逐面加固——漏一个入口等于白加十道锁。


2. SQL 注入:预处理是第一道闸

攻击:把 SQL 片段拼进查询,改变原意:

// 致命反例:字符串拼接
$sql = "SELECT * FROM users WHERE id = " . $_GET['id'];
// 输入:1 OR 1=1  → 全表泄露

// 输入:1; DROP TABLE users; --  → 删表

正确:PDO 预处理 + 占位符(见 /php-mysql-database/):

$stmt = $pdo->prepare('SELECT * FROM users WHERE id = ?');
$stmt->execute([$_GET['id']]);   // 参数作为数据,不参与 SQL 解析

附加防线:

// 白名单校验
$allowed = ['id', 'name', 'email'];
if (!in_array($orderBy, $allowed, true)) { $orderBy = 'id'; }

// 强制类型(数字型字段)
$id = (int) $_GET['id'];

注入防护层次:① 预处理(必须)→ ② 类型强制 → ③ 白名单 → ④ 最小权限数据库账号。


3. XSS:输出转义的双向战线

存储型 XSS:用户输入 <script> 存库,别人浏览时执行;反射型:直接拼进响应。

// 致命反例:未转义输出
echo "你好,{$_GET['name']}";
// 输入:<script>alert(document.cookie)</script>

正确防护:

// 输出到 HTML 上下文 → htmlspecialchars 转义
echo '你好,' . htmlspecialchars($name, ENT_QUOTES, 'UTF-8');

// 输出到 JavaScript 上下文 → 用 json_encode 而非拼接
echo '<script>const name = ' . json_encode($name, JSON_HEX_TAG | JSON_HEX_AMP) . ';</script>';
上下文转义方式
HTML 文本/属性htmlspecialchars($s, ENT_QUOTES)
JavaScriptjson_encode + HEX 旗标
URLurlencode / rawurlencode
CSS尽量避免用户进 CSS

Laravel 帮了忙:Blade 的 {{ }} 自动转义({!! !!} 才裸输出)。

铁律:输入校验防「坏数据入库」,输出转义防「坏数据执行」——两者都要,双向战线。


4. CSRF:跨站请求伪造

攻击:受害者已登录,攻击者在别的站点诱导其浏览器向你的站点发请求:

<!-- 攻击站点上的 img -->
<img src="https://bank.com/transfer?to=hacker&amount=1000">

Cookie 会自动带上 → 请求看似来自受害者。

防护:CSRF 令牌(每次表单带随机 token,服务端校验):

// 生成并校验(Laravel 已内置)
// 视图
<form method="POST">
  @csrf
  <input name="amount">
</form>

// 请求校验(中间件 VerifyCsrfToken)
// 非表单请求:X-CSRF-TOKEN 头

要点:

场景做法
表单 POST带 token 字段
API(无 Cookie 认证)可免 CSRF(token 认证本身防)
SameSite CookieSameSite=Lax 兜底
换绑/提现等敏感二次确认 + token

记忆:CSRF 防的是「借刀杀人」——令牌就是验明「这把刀是你本人举的」。


5. 会话安全:固定与劫持

会话固定攻击:攻击者先设好 PHPSESSID 发给受害者登录 → 攻击者用同一 SID 冒充。

会话劫持:窃取 SID(XSS 偷 Cookie、网络嗅探)。

防护清单:

// 会话 Cookie 配置(php.ini 或 session_set_cookie_params)
session_set_cookie_params([
    'httponly' => true,      // JS 读不到 Cookie(防 XSS 偷)
    'secure'   => true,      // 仅 HTTPS 传输
    'samesite' => 'Lax',     // 防跨站携带
]);

// 登录成功后必须重新生成 SID(防固定攻击)
session_regenerate_id(true);

// 超时与最大生命周期
ini_set('session.gc_maxlifetime', 1800);
措施防什么
HttpOnly CookieXSS 偷 SID
Secure明文网络嗅探
session_regenerate_id 登录后会话固定
超时过期闲置被利用
设备/指纹校验异常登录(进阶)

铁律:登录成功 = SID 换新的时刻——这一步忘了,其余全白搭。


6. 文件上传漏洞

上传是 PHP 应用高危区:直接放 web 可执行目录 = RCE。

// 危险反例
move_uploaded_file($_FILES['file']['tmp_name'], 'uploads/' . $_FILES['file']['name']);

// 正确:白名单扩展名 + 随机名 + 非执行目录
$allowed = ['jpg', 'png', 'gif', 'webp'];
$ext = strtolower(pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION));
if (!in_array($ext, $allowed, true)) {
    throw new RuntimeException('不支持的文件类型');
}
$newName = bin2hex(random_bytes(16)) . '.' . $ext;   // 随机名
move_uploaded_file($tmp, storage_path('uploads/' . $newName));  // 放 web 目录外

完整清单:

检查说明
扩展名白名单别用黑名单
MIME 校验只作辅助(可伪造)
内容头检测getimagesize 验图
随机文件名防路径穿越/覆盖
存储位置web 目录外,经路由服务
大小限制upload_max_filesize
病毒扫描生产必备
图片二次处理去 exif/重新编码

7. SSRF 与开放重定向

SSRF(服务端请求伪造):用户控制 URL,服务器代打内网:

// 危险:curl 直接请求用户给的 URL → 可访问 127.0.0.1 内部服务
$res = file_get_contents($_GET['url']);

// 防护:白名单域名 + 禁止内网地址
$allowed = ['https://api.example.com', 'https://cdn.example.com'];
if (!in_array(parse_url($url, PHP_URL_HOST), $allowed, true)) {
    throw new RuntimeException('URL 不在白名单');
}
// 或解析后验证 IP 不是内网段

开放重定向:header('Location: ' . $_GET['next']) 被用于钓鱼。

防护:只重定向到本站路径(相对路径),或用白名单域名表。

SSRF 关键:「服务器能访问的内网」是攻击者的跳板——URL 必须白名单,IP 必须排除内网段(127.x、10.x、172.16-31、192.168)。


8. 敏感数据与密钥管理

密钥泄露是终极翻车——.env 进 Git、日志打密码、响应返 token。

防护清单:

// 1. 密钥不进代码/仓库
// .env 加入 .gitignore,CI 用环境变量注入

// 2. 敏感数据脱敏
$logger->info('用户注册', ['email' => maskEmail($email)]);   // a***@x.com

// 3. 密码哈希(绝不 MD5/SHA 裸存)
$hash = password_hash($raw, PASSWORD_BCRYPT, ['cost' => 12]);
password_verify($input, $hash);

// 4. 传输加密
// 全站 HTTPS + HSTS 头

// 5. 最小权限
// 数据库账号只给需要的库/表权限
数据处理
密码bcrypt/argon2 哈希
token/密钥环境变量,日志脱敏
个人数据加密存储 + 访问审计
响应不返回明文密码/token 冗余字段

记忆:密钥的敌人是「不小心」——环境变量注入、日志脱敏、响应裁剪,三管齐下。


9. Laravel 内置防护清单

用框架就免写一半:

Laravel 内置防护
VerifyCsrfToken 中间件CSRF 默认开启
{{ }} 模板转义XSS 默认转义
DB:: / Eloquent 参数绑定SQL 注入默认防护
password_hash 辅助密码哈希
Auth 门面认证与会话
RateLimiter暴力破解限流
.env 不进库密钥管理约定
Storage 私有盘上传隔离

仍需自己做的:业务级授权(policy)、上传白名单、第三方回调校验、SSRF 白名单、审计日志。

// 示例:授权(Policy)——资源级访问控制
class OrderPolicy {
    public function view(User $user, Order $order): bool {
        return $user->id === $order->user_id;
    }
}

10. 安全自检速查表

检查项通过标准
SQL 注入全部预处理占位符,无拼接
XSS输出全转义,无裸 echo 用户数据
CSRF表单带 token,SameSite=Lax
会话HttpOnly + Secure + 登录后换 SID
上传白名单 + 随机名 + web 外目录
SSRFURL 白名单 + 内网 IP 排除
密钥.env 不进库、日志脱敏
密码bcrypt/argon2
HTTPS全站 + HSTS
限流登录/接口有速率限制
依赖composer.lock 锁定 + 漏洞扫描

一句话记忆:注入走预处理、XSS 走转义、CSRF 走令牌、会话登录换 SID、上传白名单随机名、SSRF 白名单、密钥进环境变量——七道闸过了,生产才敢开。


延伸阅读

  • /php-mysql-database/ — 预处理与注入防护落地
  • /php-api-design-rest/ — API 的认证与速率限制
  • /php-laravel-internals/ — 中间件管道的安全接入点
  • [[security]] — 系统安全的纵深防御
  • [[network]] — 传输层与 TLS

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「php」更多文章

  1. PHP 面向对象与设计模式:SOLID、常用模式与 Laravel 实践
  2. PHP 静态分析与代码质量:PHPStan、Psalm、Rector 与 CI 门禁
  3. PHP 部署运维实战:Nginx、PHP-FPM、Docker 与 CI/CD