URL跳轉漏洞
URL 跳轉漏洞是指后台服務器在告知瀏覽器跳轉時,未對客戶端傳入的重定向地址進行合法性校驗,導致用戶瀏覽器跳轉到釣魚頁面的一種漏洞。
使用場景
現在 Web 登錄很多都接入了QQ、微信、新浪等第三方登錄,以 QQ 第三方授權登錄為例說明,在我們調用 QQ 授權服務器進行授權時,會在參
數中傳入redirect_url(重定向)地址,告知 QQ 授權服務器,授權成功之后頁面跳轉到這個地址,然后進行站點登錄操作。但是如果你的重定向地址在
傳輸過程中被篡改成了一個釣魚網址,那么就是導致用戶的授權信息被非法獲取。當然,QQ 第三方登錄,也會有自己的策略,就是接入 QQ 第三方
登錄的應用,會在開發者平台,配置相關的跳轉白名單,只有屬於白名單中的域名、子域名或 url ,QQ授權服務器才跳轉,如果發現 redirect_url 不
合法,則跳轉到非法頁面。
防御策略
根據上面的場景分析,我們知道,之所以會出現跳轉 URL 漏洞,就是因為服務端沒有對客戶端傳遞的跳轉地址進行合法性校驗,所以,預防這種攻
擊的方式,就是對客戶端傳遞過來的跳轉 URL 進行校驗。
常用的方式:
服務端配置跳轉白名單或域名白名單,只對合法的 URL 做跳轉
下面是關於PHP服務端對客戶端傳遞過來的跳轉 URL 進行校驗的代碼:
<?php
// $allowedDomains 表示允許跳轉的url白名單
$allowedDomains = array(
"aaaa.com"
"bbbb.com"
.......
);
function encodeUrl($urlInfo)
{/*{{{*/
$path = isset($urlInfo['path']) ? $urlInfo['path'] : '';
if(!empty($path))
{
$t = explode("/", $path);
for($i = 0; $i < count($t); $i++)
{
$t[$i] = rawurlencode($t[$i]);
}
$path = implode("/", $t);
}
$query = isset($urlInfo['query']) ? $urlInfo['query'] : '';
if(!empty($query))
{
$t = explode("&", $query);
for($i = 0; $i < count($t); $i++)
{
$tt = explode("=", $t[$i]);
$tt[1] = rawurlencode($tt[1]);
$t[$i] = implode("=", $tt);
}
$query = implode("&", $t);
}
if(!isset($urlInfo['host']) || empty($urlInfo['host']))
{
return $path. "?". $query;
}
$scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : 'http';
$port = isset($urlInfo['port']) ? $urlInfo['port'] : 80;
$request = $scheme . '://'. $urlInfo['host'];
$request .= ($port == 80) ? '' : ':'.$port;
$request .= $path;
$request .= (empty($query)) ? '' : '?'.$query;
return $request;
}/*}}}*/
function checkUrl($url,$domainArr=array())
{/*{{{*/
$res = array('isTrustedDomain' => false,'url' => '','domain' => '');
if(empty($url)) return $res;
$domainArr = empty($domainArr) || !is_array($domainArr) ? $allowedDomains : $domainArr;
$url = filterUrl($url);//先過濾特殊字符
$p = parse_url($url);
$scheme = $p['scheme'];
if(!in_array(strtolower($scheme),array('http','https'))){
return $res;
}
$host = $p['host'];
if(!isValidHost($host)){
return $res;
}
$hostLen = strlen($host);
foreach($domainArr as $domain){
$firstPos = strpos($host, $domain);
if($firstPos !== false && ($firstPos + strlen($domain)) == $hostLen){
if($firstPos == 0 || $domain[0] == '.' || $host[$firstPos-1] == '.'){
$res['isTrustedDomain'] = true;
$res['url'] = $url;
$res['domain'] = $domain;
break;
}
}
}
return $res;
}/*}}}*/
function filterUrl( $url )
{/*{{{*/
if(empty($url)) return $url;
// Strip all of the Javascript in script tags out...
$url = preg_replace('/<SCRIPT.*?<\/SCRIPT>/ims',"",$url);
// Strip all blank character
$url = preg_replace('/[\s\v\0]+/',"",$url);
//Strip special characters(',",<,>,\)
$url = str_replace(array("'","\"","<",">","\\"),'',$url);
return $url;
}/*}}}*/
function isValidHost($host)
{/*{{{*/
$p = "/^[0-9a-zA-Z\-\.]+$/";
return preg_match($p,$host) ? true : false;
}/*}}}*/
$url = "https://www.baidu.com";
$call_back_url = trim($url);
$call_back_url = encodeUrl(parse_url(urldecode($call_back_url)));
$res = checkUrl($call_back_url, $domainArr);
var_dump($res);
