首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 判斷來源網頁 (HTTP_REFERER):防盜鏈、CSRF 防護與可靠性分析

     編輯:PHP 相關知識

$_SERVER['HTTP_REFERER'] 是判斷請求來源的常用手段,但極不可靠。本文分析其局限性,提供防盜鏈、CSRF 防護的正確實作方案。

HTTP_REFERER 本質與局限

特性說明
來源瀏覽器自動發送 (非強制)
可偽造極易 (curl -H "Referer: ...", 瀏覽器擴充套件)
可為空直接輸入網址、書籤、HTTPS→HTTP、隱私模式、安全軟體
可被移除<meta name="referrer" content="no-referrer">, Referrer-Policy
跨域限制預設同源才發送,跨域需 Referrer-Policy: unsafe-url

基本用法 (僅供參考,不可信任)

<?php
$referer = $_SERVER['HTTP_REFERER'] ?? '';

if ($referer) {
    $parsed = parse_url($referer);
    $host = $parsed['host'] ?? '';
    
    // 簡易同源檢查
    $allowedHosts = ['example.com', 'www.example.com', 'api.example.com'];
    if (in_array($host, $allowedHosts)) {
        echo "來源可信: $host";
    } else {
        echo "外部來源: $host";
    }
} else {
    echo "無 Referer (直接訪問/隱私模式/被移除)";
}
?>

正確防盜鏈方案:Token/簽名驗證

<?php
// 防盜鏈核心:簽名 URL,而非信任 Referer
class HotlinkProtector {
    private string $secret;
    private int $ttl; // 秒

    public function __construct(string $secret, int $ttl = 3600) {
        $this->secret = $secret;
        $this->ttl = $ttl;
    }

    // 產生受保護 URL
    public function signUrl(string $path, array $params = []): string {
        $params['expires'] = time() + $this->ttl;
        $params['signature'] = $this->generateSignature($path, $params);
        return $path . '?' . http_build_query($params);
    }

    // 驗證請求
    public function validate(string $path, array $query): bool {
        if (!isset($query['expires'], $query['signature'])) return false;
        if ($query['expires'] < time()) return false; // 過期
        
        $expected = $this->generateSignature($path, $query);
        return hash_equals($expected, $query['signature']);
    }

    private function generateSignature(string $path, array $params): string {
        $data = $path . '?' . http_build_query(array_diff_key($params, ['signature' => '']));
        return hash_hmac('sha256', $data, $this->secret);
    }
}

// 使用
$protector = new HotlinkProtector('your-secret-key');

// 產生圖片 URL (有效期 1 小時)
$imgUrl = $protector->signUrl('/images/photo.jpg', ['w' => 800]);
// 輸出: /images/photo.jpg?w=800&expires=1234567890&signature=abc123...

// 驗證端點
if (!$protector->validate($_SERVER['REQUEST_URI'], $_GET)) {
    http_response_code(403);
    exit('存取被拒絕:連結無效或已過期');
}
// 正常輸出圖片...
?>

正確 CSRF 防護:同步器令牌模式

<?php
// CSRF 核心:Session 中儲存 Token,表單提交時驗證
class CsrfGuard {
    private const TOKEN_NAME = 'csrf_token';
    private const HEADER_NAME = 'X-CSRF-Token';

    public static function generate(): string {
        if (empty($_SESSION[self::TOKEN_NAME])) {
            $_SESSION[self::TOKEN_NAME] = bin2hex(random_bytes(32));
        }
        return $_SESSION[self::TOKEN_NAME];
    }

    public static function validate(string $token): bool {
        if (empty($_SESSION[self::TOKEN_NAME]) || empty($token)) return false;
        return hash_equals($_SESSION[self::TOKEN_NAME], $token);
    }

    public static function rotate(): string {
        $_SESSION[self::TOKEN_NAME] = bin2hex(random_bytes(32));
        return $_SESSION[self::TOKEN_NAME];
    }

    // 中介層自動驗證 (除 GET/HEAD/OPTIONS)
    public static function middleware(): void {
        if (in_array($_SERVER['REQUEST_METHOD'], ['GET', 'HEAD', 'OPTIONS'])) return;
        
        $token = $_POST[self::TOKEN_NAME] 
            ?? $_SERVER['HTTP_' . str_replace('-', '_', strtoupper(self::HEADER_NAME))]
            ?? '';
            
        if (!self::validate($token)) {
            http_response_code(419); // CSRF Token Mismatch
            exit('CSRF 驗證失敗,請重新整理頁面');
        }
    }
}

// 表單加入 Token
// <input type="hidden" name="csrf_token" value="">

// AJAX 設定 Header
// fetch('/api/data', { headers: { 'X-CSRF-Token': document.querySelector('[name=csrf_token]').value } })
?>

Referrer-Policy 最佳設定

<!-- HTML meta 標籤 -->
<meta name="referrer" content="strict-origin-when-cross-origin">

<!-- 或 HTTP Header (Nginx/Apache/PHP) -->
<?php
// PHP 設定
header('Referrer-Policy: strict-origin-when-cross-origin');
?>

# Nginx
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

# Apache
Header always set Referrer-Policy "strict-origin-when-cross-origin"

Referrer-Policy 值對照

值行為推薦場景
no-referrer完全不發送高隱私需求
strict-origin-when-cross-origin同源完整 URL,跨域僅 Origin,HTTPS→HTTP 不發送推薦預設
origin-when-cross-origin同源完整 URL,跨域僅 Origin需跨域 Origin
unsafe-url永遠發送完整 URL極少數分析需求

總結:何時可用 Referer?

  • ✅ 分析統計:GA、Matomo 等分析工具 (接受不準確)
  • ✅ 使用者體驗:「返回上一頁」連結、顯示來源頁面標題
  • ❌ 安全決策:防盜鏈、權限控制、CSRF 防護、支付回調驗證
  • ❌ 業務邏輯:依據 Referer 判斷功能開關

核心原則:HTTP_REFERER 僅供參考,安全相關決策必須使用加密簽名、CSRF Token、JWT 等可驗證機制。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved