利用 HTTP 重定向機制,將目標 URL 作為查詢參數傳遞給中轉頁面,中轉頁面解析參數後發送 302/301 跳轉響應。
<?php
// 1. 獲取目標 URL 參數
$targetUrl = isset($_GET['url']) ? $_GET['url'] : '';
// 2. 安全驗證 (關鍵:防開放重定向攻擊)
$allowedDomains = ['234.com', 'partner.com', 'trusted.org'];
$parsedUrl = parse_url($targetUrl);
if (empty($targetUrl) || !isset($parsedUrl['host'])) {
die('無效的跳轉參數');
}
if (!in_array($parsedUrl['host'], $allowedDomains)) {
die('不允許跳轉到該域名: ' . $parsedUrl['host']);
}
// 3. 可選:記錄跳轉日志
$log = date('Y-m-d H:i:s') . " | IP: {$_SERVER['REMOTE_ADDR']} | URL: $targetUrl\n";
file_put_contents('redirect.log', $log, FILE_APPEND);
// 4. 執行重定向
header("Location: $targetUrl", true, 302); // 302 臨時重定向
exit();
?>
<%
' 1. 獲取參數
targetUrl = Request.QueryString("url")
' 2. 基礎驗證
If targetUrl = "" Then
Response.Write "缺少 url 參數"
Response.End
End If
' 3. 域名白名單驗證 (簡易版)
Dim allowedDomains, domain, isAllowed
allowedDomains = Array("234.com", "partner.com")
isAllowed = False
Set regEx = New RegExp
regEx.Pattern = "^https?://([^/]+)"
regEx.IgnoreCase = True
Set matches = regEx.Execute(targetUrl)
If matches.Count > 0 Then
domain = matches(0).SubMatches(0)
For Each d In allowedDomains
If InStr(1, domain, d, 1) > 0 Then
isAllowed = True
Exit For
End If
Next
End If
If Not isAllowed Then
Response.Write "禁止跳轉到: " & domain
Response.End
End If
' 4. 記錄日志 (可選)
Set fso = CreateObject("Scripting.FileSystemObject")
Set logFile = fso.OpenTextFile(Server.MapPath("redirect.log"), 8, True)
logFile.WriteLine Now & " | " & Request.ServerVariables("REMOTE_ADDR") & " | " & targetUrl
logFile.Close
' 5. 執行跳轉
Response.Redirect targetUrl
%>
瀏覽器訪問: https://123.com/?url=https://234.com/23.html
服務端響應: 302 Found
Location: https://234.com/23.html
瀏覽器自動跳轉到目標頁面
| 風險 | 防護措施 |
|---|---|
| 開放重定向 (Open Redirect) | 嚴格域名白名單、拒絕相對路徑、驗證協議 |
| 釣魚攻擊利用 | 跳轉前顯示確認頁「即將跳轉到 example.com」 |
| XSS 注入 | 對 URL 參數進行編碼輸出、設置 CSP 頭 |
| 參數篡改 | 簽名驗證: ?url=...&sign=hmac_sha256(url, secret) |
| 循環重定向 | 限制跳轉次數、檢查 Referer |
// PHP 生成安全鏈接
function generateSafeRedirectUrl($targetUrl, $secretKey, $expire = 3600) {
$timestamp = time() + $expire;
$data = $targetUrl . '|' . $timestamp;
$sign = hash_hmac('sha256', $data, $secretKey);
return "https://123.com/redirect.php?url=" . urlencode($targetUrl)
. "&t=$timestamp&sign=$sign";
}
// redirect.php
$secretKey = 'your_shared_secret';
$targetUrl = $_GET['url'];
$timestamp = (int)$_GET['t'];
$sign = $_GET['sign'];
// 1. 過期檢查
if ($timestamp < time()) {
die('鏈接已過期');
}
// 2. 簽名驗證
$data = $targetUrl . '|' . $timestamp;
$expectedSign = hash_hmac('sha256', $data, $secretKey);
if (!hash_equals($expectedSign, $sign)) {
die('簽名驗證失敗');
}
// 3. 執行跳轉 (含白名單驗證)
header("Location: $targetUrl");
# nginx.conf
location /redirect {
set $target_url $arg_url;
# 白名單校驗
if ($target_url !~* "^https?://(234\.com|partner\.com)/") {
return 403 "Forbidden domain";
}
# 直接 302 跳轉
return 302 $target_url;
}