PHP 防注入、XSS、CSRF 完整安全代码示例

iT日记 网络攻防

PHP 防注入、XSS、CSRF 完整安全代码示例-第1张图片-iT日记

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());
}
?>4

2. 高危操作二次验证

修改管理员密码、删除数据、新增用户等高危操作,必须要求用户输入旧密码 / 验证码,即使 Token 被绕过,也无法执行高危操作。

四、避坑指南与长期安全建议

1、永远不要信任客户端的任何输入,所有 $_GET、$_POST、$_REQUEST 接收的参数,必须先过滤、验证,再使用

2、前端验证只能优化体验,不能作为安全防护,所有前端验证都可以被绕过,后端必须做双重验证

3、不要使用过时的防护方案,addslashes()、mysql_escape_string() 无法完全防注入,必须使用预处理语句

4、配合服务器层面加固,结合之前的宝塔安全加固方案,禁用 PHP 危险函数、开启防火墙、定期查杀木马,形成多层防护

标签: PHP 防注入 XSS 跨站防护 CSRF 防御代码 PHP 安全开发 网站漏洞修复 CMS 安全加固

上一篇恶意代码分析入门:识别病毒与木马的核心方法揭秘

下一篇当前分类已是最新一篇