
PHP 开发的网站中,80% 的安全入侵、数据泄露、页面篡改事件,都源于 SQL 注入、XSS 跨站脚本、CSRF 跨站请求伪造 这三类基础漏洞。尤其是 ZBlog、MetInfo 等 CMS 的二次开发、自定义插件、表单功能中,新手很容易忽略基础防护,导致网站被拖库、挂马、管理员权限被窃取。
本文结合站长日常开发场景,提供可直接复制使用、零门槛落地的安全代码示例,覆盖三类漏洞的核心防御方案,新手也能直接套用在自己的站点功能中。
一、SQL 注入防护(最致命,直接导致数据库泄露)
漏洞风险
SQL 注入的核心成因,是直接将用户输入的参数拼接到 SQL 语句中执行,攻击者可通过构造恶意参数,实现查询敏感数据、删库、篡改管理员密码、获取服务器权限等操作,是 PHP 站点最致命的漏洞之一。
❌ 绝对禁止的错误写法
// 直接拼接用户输入,100% 会被注入攻击 $id = $_GET['id']; $sql = "SELECT * FROM article WHERE id = ".$id; $result = mysql_query($sql); // 字符串查询同理,无过滤直接拼接 $username = $_POST['username']; $sql = "SELECT * FROM user WHERE username = '".$username."'";
✅ 最优防御方案:PDO 预处理语句(预编译)
预编译是防 SQL 注入的黄金标准,SQL 语句和用户参数完全分离,数据库只会将参数当作纯文本处理,不会执行任何注入逻辑,兼容所有 PHP 主流版本。
1. 封装 PDO 连接与查询基类(直接复制可用)
<?php
// 数据库配置
$db_host = '127.0.0.1';
$db_user = '你的数据库用户名';
$db_pwd = '你的数据库密码';
$db_name = '你的数据库名';
$db_charset = 'utf8mb4';
// 初始化PDO连接
try {
$pdo = new PDO("mysql:host=$db_host;dbname=$db_name;charset=$db_charset", $db_user, $db_pwd);
// 设置PDO错误模式为异常
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// 禁用模拟预处理,确保真正的参数绑定
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
} catch (PDOException $e) {
die("数据库连接失败:" . $e->getMessage());
}
?>2. 常用场景预处理示例
① 单条数据查询(文章详情、用户信息)
<?php
// 接收用户参数
$id = $_GET['id'];
// 预处理SQL,用?占位符代替参数
$sql = "SELECT id,title,content FROM article WHERE id = ?";
$stmt = $pdo->prepare($sql);
// 绑定参数并执行
$stmt->execute([$id]);
// 获取结果
$article = $stmt->fetch(PDO::FETCH_ASSOC);
// 输出结果
if ($article) {
echo "文章标题:" . $article['title'];
} else {
echo "文章不存在";
}
?>② 数据插入(用户注册、评论提交)
<?php // 接收表单参数 $username = $_POST['username']; $email = $_POST['email']; $content = $_POST['content']; // 预处理SQL,命名占位符写法,更清晰 $sql = "INSERT INTO comment (username,email,content,addtime) VALUES (:username,:email,:content,now())"; $stmt = $pdo->prepare($sql); // 绑定参数执行 $stmt->execute([ ':username' => $username, ':email' => $email, ':content' => $content ]); // 获取插入的自增ID echo "评论提交成功,ID:" . $pdo->lastInsertId(); ?>
③ 数据更新(信息修改、状态更新)
<?php $id = $_POST['id']; $title = $_POST['title']; $sql = "UPDATE article SET title = :title WHERE id = :id"; $stmt = $pdo->prepare($sql); $stmt->execute([ ':title' => $title, ':id' => $id ]); // 输出影响行数 echo "更新成功,影响行数:" . $stmt->rowCount(); ?>
✅ CMS 场景适配提示
ZBlog、MetInfo 等主流 CMS 均自带封装好的数据库操作类,严禁自己拼接原生 SQL,直接使用框架自带的查询方法,底层已做好预处理防护,示例:
// ZBlogPHP 原生查询写法(自带防注入) $article = $zbp->GetArticleByID($_GET['id']);
二、XSS 跨站脚本攻击防护(最常见,易导致页面篡改、权限窃取)
漏洞风险
XSS 漏洞的核心成因,是直接将用户输入的内容未经过滤输出到页面中,攻击者可植入恶意 JavaScript 脚本,实现窃取管理员 Cookie、跳转博彩网站、篡改页面内容、诱导用户钓鱼等操作,分为存储型(存入数据库,所有访问者中招)和反射型(通过链接传播,针对性攻击)两类。
❌ 绝对禁止的错误写法
// 直接输出用户输入,无任何过滤,100% 存在XSS漏洞 echo $_GET['keyword']; echo $_POST['content']; // 评论区、留言板直接输出用户提交内容,极易被植入恶意脚本 <?php foreach ($commentList as $item): ?> <div class="comment"> <span><?php echo $item['username']; ?></span> <p><?php echo $item['content']; ?></p> </div> <?php endforeach; ?>
✅ 核心防御:输出转义(100% 拦截基础 XSS)
PHP 内置的 htmlspecialchars() 函数,会将 HTML 特殊字符转为实体字符,让恶意脚本无法被浏览器解析执行,是防 XSS 的核心手段。
1. 正确的转义写法(固定参数,新手别写错)
<?php
// 核心转义函数,固定参数,适配所有场景
function xss_filter($str) {
return htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
}
// 输出示例
$keyword = $_GET['keyword'];
$username = $_POST['username'];
$content = $_POST['content'];
// 安全输出
echo "搜索关键词:" . xss_filter($keyword);
echo "用户名:" . xss_filter($username);
echo "评论内容:" . xss_filter($content);
?>2. 模板循环输出安全写法
<?php foreach ($commentList as $item): ?> <div class="comment"> <span><?php echo xss_filter($item['username']); ?></span> <p><?php echo xss_filter($item['content']); ?></p> </div> <?php endforeach; ?>
✅ 进阶防护方案
1. 输入过滤(存储前拦截恶意内容)
针对评论、留言等用户提交的内容,存储到数据库前先过滤敏感标签,适配 CMS 评论场景:
<?php
function input_filter($str) {
// 禁止script、iframe、form等危险标签
$filter_arr = [
'/<script.*?>.*?<\/script>/is',
'/<iframe.*?>.*?<\/iframe>/is',
'/<form.*?>.*?<\/form>/is',
'/onerror=.*?/is',
'/onclick=.*?/is',
'/onload=.*?/is'
];
$str = preg_replace($filter_arr, '', $str);
// 去除多余空格与换行
$str = trim($str);
return $str;
}
// 使用示例:提交评论时先过滤,再存入数据库
$content = input_filter($_POST['content']);
?>2. HTTP-only Cookie 防护
防止恶意脚本窃取管理员 Cookie,即使存在 XSS 漏洞,也无法拿到用户的登录凭证,PHP 直接设置:
<?php
// 数据库配置
$db_host = '127.0.0.1';
$db_user = '你的数据库用户名';
$db_pwd = '你的数据库密码';
$db_name = '你的数据库名';
$db_charset = 'utf8mb4';
// 初始化PDO连接
try {
$pdo = new PDO("mysql:host=$db_host;dbname=$db_name;charset=$db_charset", $db_user, $db_pwd);
// 设置PDO错误模式为异常
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// 禁用模拟预处理,确保真正的参数绑定
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
} catch (PDOException $e) {
die("数据库连接失败:" . $e->getMessage());
}
?>0宝塔面板可直接在 PHP 配置文件中修改上述参数,永久生效。
3. CSP 内容安全策略
通过 Nginx 配置,限制页面只能加载本站域名的资源,禁止执行内联恶意脚本,彻底阻断 XSS 脚本执行,在站点 Nginx 配置中添加:
<?php
// 数据库配置
$db_host = '127.0.0.1';
$db_user = '你的数据库用户名';
$db_pwd = '你的数据库密码';
$db_name = '你的数据库名';
$db_charset = 'utf8mb4';
// 初始化PDO连接
try {
$pdo = new PDO("mysql:host=$db_host;dbname=$db_name;charset=$db_charset", $db_user, $db_pwd);
// 设置PDO错误模式为异常
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// 禁用模拟预处理,确保真正的参数绑定
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
} catch (PDOException $e) {
die("数据库连接失败:" . $e->getMessage());
}
?>1三、CSRF 跨站请求伪造防护(易忽略,可导致管理员权限被篡改)
漏洞风险
CSRF 漏洞的核心成因,是浏览器会自动携带站点的 Cookie 发起请求,攻击者可诱导已登录的管理员点击恶意链接,在用户不知情的情况下,自动执行改密码、删文章、新增管理员、修改站点配置等高危操作。
✅ 核心防御方案:Token 令牌验证(行业通用标准)
核心逻辑:每次加载表单页面时,生成一个唯一的随机 Token,存入 Session 中;表单提交时,必须携带这个 Token,后端验证 Token 与 Session 中的是否一致,一致才允许执行操作,第三方站点无法获取到正确的 Token,彻底阻断 CSRF 攻击。
1. 完整 Token 验证代码示例
① 表单页面(新增 / 编辑文章、修改密码等)
<?php
// 数据库配置
$db_host = '127.0.0.1';
$db_user = '你的数据库用户名';
$db_pwd = '你的数据库密码';
$db_name = '你的数据库名';
$db_charset = 'utf8mb4';
// 初始化PDO连接
try {
$pdo = new PDO("mysql:host=$db_host;dbname=$db_name;charset=$db_charset", $db_user, $db_pwd);
// 设置PDO错误模式为异常
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// 禁用模拟预处理,确保真正的参数绑定
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
} catch (PDOException $e) {
die("数据库连接失败:" . $e->getMessage());
}
?>2② 后端处理页面(submit.php)
<?php
// 数据库配置
$db_host = '127.0.0.1';
$db_user = '你的数据库用户名';
$db_pwd = '你的数据库密码';
$db_name = '你的数据库名';
$db_charset = 'utf8mb4';
// 初始化PDO连接
try {
$pdo = new PDO("mysql:host=$db_host;dbname=$db_name;charset=$db_charset", $db_user, $db_pwd);
// 设置PDO错误模式为异常
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// 禁用模拟预处理,确保真正的参数绑定
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
} catch (PDOException $e) {
die("数据库连接失败:" . $e->getMessage());
}
?>3✅ 辅助防御方案
1. Referer 来源验证
验证请求的来源域名是否为自己的站点,拒绝第三方站点的请求,简单易用,可作为辅助防护:
<?php
// 数据库配置
$db_host = '127.0.0.1';
$db_user = '你的数据库用户名';
$db_pwd = '你的数据库密码';
$db_name = '你的数据库名';
$db_charset = 'utf8mb4';
// 初始化PDO连接
try {
$pdo = new PDO("mysql:host=$db_host;dbname=$db_name;charset=$db_charset", $db_user, $db_pwd);
// 设置PDO错误模式为异常
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// 禁用模拟预处理,确保真正的参数绑定
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
} catch (PDOException $e) {
die("数据库连接失败:" . $e->getMessage());
}
?>42. 高危操作二次验证
修改管理员密码、删除数据、新增用户等高危操作,必须要求用户输入旧密码 / 验证码,即使 Token 被绕过,也无法执行高危操作。
四、避坑指南与长期安全建议
1、永远不要信任客户端的任何输入,所有 $_GET、$_POST、$_REQUEST 接收的参数,必须先过滤、验证,再使用
2、前端验证只能优化体验,不能作为安全防护,所有前端验证都可以被绕过,后端必须做双重验证
3、不要使用过时的防护方案,addslashes()、mysql_escape_string() 无法完全防注入,必须使用预处理语句
4、配合服务器层面加固,结合之前的宝塔安全加固方案,禁用 PHP 危险函数、开启防火墙、定期查杀木马,形成多层防护
标签: PHP 防注入 XSS 跨站防护 CSRF 防御代码 PHP 安全开发 网站漏洞修复 CMS 安全加固


