ThinkPHP项目点击劫持防御

wen PHP项目 3

本文目录导读:

ThinkPHP项目点击劫持防御

  1. HTTP响应头防御(最常用)
  2. Nginx层防御(服务器配置)
  3. Apache层防御
  4. 前端页面防御(JavaScript方式)
  5. 完整的中间件实现(含多策略)
  6. 配置文件方式
  7. 推荐使用组合防御

在ThinkPHP项目中防御点击劫持(Clickjacking)攻击,可以通过以下几种方式实现:

HTTP响应头防御(最常用)

使用中间件方式(推荐)

app/middleware.php 中注册全局中间件:

<?php
// app/middleware.php
return [
    // 全局请求缓存
    // \think\middleware\CheckRequestCache::class,
    // 多语言加载
    // \think\middleware\LoadLangPack::class,
    // Session初始化
    // \think\middleware\SessionInit::class,
    // 添加防点击劫持中间件
    \app\middleware\ClickjackingProtection::class,
];

创建中间件 app/middleware/ClickjackingProtection.php

<?php
namespace app\middleware;
use Closure;
use think\Request;
use think\Response;
class ClickjackingProtection
{
    public function handle(Request $request, Closure $next)
    {
        $response = $next($request);
        // 设置X-Frame-Options响应头
        $response->header([
            'X-Frame-Options' => 'SAMEORIGIN', // 或者 'DENY'
            'Content-Security-Policy' => "frame-ancestors 'self'",
        ]);
        return $response;
    }
}

在控制器中动态设置

<?php
namespace app\controller;
use think\Request;
class Index
{
    public function index(Request $request)
    {
        // 设置防iframe加载
        header('X-Frame-Options: SAMEORIGIN');
        header("Content-Security-Policy: frame-ancestors 'self'");
        return view();
    }
}

使用路由中间件

route/app.php 中对特定路由添加中间件:

<?php
use think\facade\Route;
// 给特定路由添加防点击劫持中间件
Route::group('admin', function () {
    Route::get('dashboard', 'admin/Dashboard/index');
    Route::post('data/save', 'admin/Data/save');
})->middleware(\app\middleware\ClickjackingProtection::class);

Nginx层防御(服务器配置)

在 Nginx 配置中添加:

server {
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header Content-Security-Policy "frame-ancestors 'self'" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
}

Apache层防御

.htaccess 或虚拟主机配置中添加:

Header always append X-Frame-Options SAMEORIGIN
Header set Content-Security-Policy "frame-ancestors 'self'"

前端页面防御(JavaScript方式)

在项目入口文件或公共模板中添加:

<!-- 在公共模板文件如 public/header.html 中添加 -->
<script>
    // 防止页面被iframe加载
    if (window.top !== window.self) {
        window.top.location = window.self.location;
        // 或者直接显示警告
        // document.body.innerHTML = "该页面不允许被嵌入iframe中";
    }
</script>

或者在公共控制器中:

<?php
namespace app\common;
class BaseController
{
    protected function preventClickjacking()
    {
        // 判断是否为iframe环境
        if (isset($_SERVER['HTTP_SEC_FETCH_DEST']) && $_SERVER['HTTP_SEC_FETCH_DEST'] === 'iframe') {
            // 返回错误页面或跳转
            return redirect('/');
        }
    }
}

完整的中间件实现(含多策略)

<?php
namespace app\middleware;
use Closure;
use think\Request;
use think\Response;
class ClickjackingProtection
{
    /**
     * 防御策略
     * allow - 允许所有
     * deny - 禁止所有
     * sameorigin - 仅允许同源
     * whitelist - 白名单
     */
    protected $strategy = 'sameorigin';
    // 白名单域名
    protected $whitelist = [
        'example.com',
        'admin.example.com'
    ];
    public function handle(Request $request, Closure $next)
    {
        $response = $next($request);
        // 检查来源
        $origin = $request->header('origin', '');
        $referer = $request->header('referer', '');
        switch ($this->strategy) {
            case 'deny':
                $response->header('X-Frame-Options', 'DENY');
                $response->header('Content-Security-Policy', "frame-ancestors 'none'");
                break;
            case 'sameorigin':
                $response->header('X-Frame-Options', 'SAMEORIGIN');
                $response->header('Content-Security-Policy', "frame-ancestors 'self'");
                break;
            case 'whitelist':
                if ($this->checkWhitelist($origin, $referer)) {
                    $response->header('X-Frame-Options', 'ALLOW-FROM ' . $origin);
                } else {
                    $response->header('X-Frame-Options', 'DENY');
                    $response->header('Content-Security-Policy', "frame-ancestors 'none'");
                }
                break;
        }
        // 添加其他安全头
        $response->header([
            'X-Content-Type-Options' => 'nosniff',
            'X-XSS-Protection' => '1; mode=block',
        ]);
        return $response;
    }
    private function checkWhitelist($origin, $referer)
    {
        $url = $origin ?: $referer;
        if (empty($url)) return true;
        $host = parse_url($url, PHP_URL_HOST);
        if (!$host) return false;
        foreach ($this->whitelist as $domain) {
            if ($host === $domain || str_ends_with($host, '.' . $domain)) {
                return true;
            }
        }
        return false;
    }
    // 设置策略的方法
    public function setStrategy($strategy)
    {
        $this->strategy = $strategy;
        return $this;
    }
}

配置文件方式

config/safe.php 中新增配置:

<?php
return [
    // 点击劫持防御
    'clickjacking' => [
        'enable' => true,
        'x_frame_options' => 'SAMEORIGIN', // DENY, SAMEORIGIN, 或自定义地址
        'csp_frame_ancestors' => "'self'", // 或具体域名列表
    ],
];

然后在中间件中读取配置:

public function handle(Request $request, Closure $next)
{
    $response = $next($request);
    $config = config('safe.clickjacking');
    if ($config['enable']) {
        $response->header('X-Frame-Options', $config['x_frame_options']);
        $response->header('Content-Security-Policy', 
            "frame-ancestors {$config['csp_frame_ancestors']}");
    }
    return $response;
}

推荐使用组合防御

建议采用多层防御策略:

  1. Nginx/Apache层:设置基础响应头
  2. ThinkPHP中间件层:应用级别的防御逻辑
  3. 前端JavaScript:提供额外保护

这样即使某一层被绕过,其他层还能提供保护,需要根据项目实际需求选择合适的防御级别,对于后台管理页面建议使用DENYSAMEORIGIN策略。

抱歉,评论功能暂时关闭!