前言
當開發者需要為不同目的以不同形式處理URL時,比如說瀏覽器歷史導航,錨點目標,查詢引數等等,我們經常會藉助於JavaScript。然而,它的頻繁使用促使攻擊者利用其漏洞。這種被利用的風險是我們必須在我們的JavaScript應用程式中實現URL驗證的原因。
URL驗證檢查URL是否遵循正確的URL語法,也就是每個URL必須具備的結構。URL驗證可以使我們的應用程式免遭基於URL的漏洞,比如惡意指令碼注入和伺服器端請求偽造(SSRF)。當我們在獲取遠端資源時沒有應用安全編碼慣例來驗證使用者提供的URL時,惡意行為者可以採用SSRF攻擊。
URL驗證
URL驗證的存在是為了加強安全,防止可能存在的漏洞,並消除執行程式碼時產生的任何錯誤的機會。但是我們應該在什麼時候使用URL驗證,在這個過程中我們要驗證什麼呢?我們應該在所有必須識別和驗證諸如網頁、圖片、gif和影片等資源的軟體中實施URL驗證。
一個典型的URL包括多個片段,比如協議、域名、主機名、資源名、URL源、埠等等。這些用來告訴瀏覽器如何追蹤指定的資源。我們可以以不同的方式來驗證URL:
- 使用正則字面量和建構函式
- URL建構函式
isValidURL
方法- Input元素
- Anchor標籤方法
一個典型的URL驗證方案接收來自使用者的輸入,然後對其進行解析,以識別其各個組成部分。驗證方案可以確保所有的URL元件符合網際網路標準。例如,如果需要,它可以檢查URL是否使用安全協議。
主機名驗證首先是將主機名分成獨立的標籤,以確保它們符合頂級域名規範。一個典型的主機名由至少兩個用點分隔的標籤組成。例如,www.snyk.com 有 "www"、"snyk"和 "com"的標籤。每個標籤只能由一個字母數字字元或一個連字元組成,無論大小寫。然後,驗證方案可以確保主機名與URL的允許列表相匹配,以確保只允許指定的URL,並且允許的URL不會被錯誤地取消資格。
預設情況下,URL中使用的大多數資源的路徑都是允許的。然而,埠只能在1到65536的範圍內。任何超出這個範圍的東西都應該丟擲一個錯誤。我們還可以檢查數字IP地址,以判斷它是一個IPV4地址還是IPV6地址。
最後,我們也可以檢查URL的使用者名稱和密碼。這個功能有助於遵守公司政策和憑證保護。
現在,你已經有了這些基礎知識,讓我們來看看使用javascript的URL驗證吧。
如何執行URL驗證
在JavaScript中,執行URL驗證最簡單的方式是使用new URL
建構函式。除此之外,它還得到了Node.js執行時和大多數瀏覽器的支援。
基本語法如下:
new URL (url)
new URL (url , base)
如果提供相對URL,JavaScript只需要base
元素。如果不提供相對URL,預設為undefined
。另外,如果提供一個具有絕對URL的base
元素,JavaScript會忽略base
元素。
為了驗證URL,可以使用以下程式碼:
function checkUrl (string) {
let givenURL ;
try {
givenURL = new URL (string);
} catch (error) {
console.log ("error is", error);
return false;
}
return true;
}
該函式用於檢查URL的有效性。當URL有效時返回true
,否則返回false
。
- 如果你傳遞
www.urlcheck.com
給該函式會返回false
。因為該引數並不是一個有效的URL。正確版本應該是https://urlcheck.com
。 - 另一個例子是
mailto:John.Doe@example.com
。這是一個有效的URL,但如果移除了冒號,JavaScript就不再認為它是一個URL了。 - 第三個例子是
ftp://
。這不是一個有效URL,因為沒有包含主機名。如果你新增兩個點(..
),就會變成有效URL。因為點會被認為是一個主機名,也就是說ftp://..
變成了一個有效的URL。
重要的是要記住,非常規的、但完全有效的URL是存在的!它們可能對從事這些工作的開發人員來說是意外的,但在其他方面是完全合適的。例如,以下兩個URL都會返回真值:
new URL("youtube://a.b.c.d");
new URL("a://1.2.3.4@1.2.3.4");
這些例子提醒我們,開發者應該依靠URL驗證原則,而不是專注於慣例。
如果你想確保有效的URL包含一些特定的URL方案,你可以使用以下函式:
function checkHttpUrl(string) {
let givenURL;
try {
givenURL = new URL(string);
} catch (error) {
console.log("error is",error)
return false;
}
return givenURL.protocol === "http:" || givenURL.protocol === "https:";
}
該函式驗證URL,然後檢查URL是否使用HTTP或者HTTPS。在這裡,ftp://..
會被認為是無效的,因為它不包含HTTP或者HTTPS,而http://..
依舊有效。
使用URL
建構函式的一些其他方式包括:
let m = '<https://snyk.io>';
let a = new URL("/", m);
上述示例使用了base
元素。記錄下這個值,我們就可以得到https://snyk.io/
。
要返回一個URL物件而不指定base
引數的話,語法是:
let b = new URL(m);
為了給主機新增一個路徑名,我們的程式碼結構如下:
let d = new URL('/en-US/docs', b);
儲存在變數d
上的URL是https://snyk.io/en-US/docs
。
URL模組的另一個功能是,它實現了WHATWG URL API,它遵守WHATWG的URL標準,供瀏覽器使用:
let adr = new URL("<https://snyk.io/en-US/docs>");
let host = adr.host;
let path = adr.pathname;
在上面的例子中,我們建立了一個名為adr
的URL物件。接著,程式碼獲取URL的主機和路徑名,分別是snyk.io
和/en-US/docs
。最後,我們可以將URL和允許列表或者黑名單進行對比,確保只有特定URL是被允許的。
如何使用正則驗證
另一種驗證URL的方法是使用正規表示式(regex)。我們可以使用Regex來檢查URL是否有效。
使用regex進行URL驗證的JavaScript語法是:
function isValidURL(string)
{
var res =
string.match(/(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-
]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]
\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|w
ww\.[a-zA-Z0-9]+\.[^\s]{2,})/gi);
return (res !== null);
};
來測試一些URL:
var tc1 = "<http://helloworld.com>"
console.log(isValidURL(tc1));
regex定義的URL語法檢查URL是否以http://
或https://
或子域開始,以及是否包含域名。控制檯上的語句結果是true
,因為它遵循了由regex定義的URL語法。相反,下面的語句將返回一個false
,因為它沒有以任何允許的方案或子域開始,也不包含域名:
var tc4 = "helloWorld";
console.log (isValidURL(tc4));
上面的正規表示式相對簡單,但仍然難以駕馭。這也是一個容易出錯的方法,因為一個正規表示式不能充分處理驗證URL的規則。它最多隻能做到匹配有效的URL。此外,當一個正規表示式要麼包含複雜的驗證邏輯,要麼收到冗長的輸入字串時,執行驗證檢查就變得很耗時。
為了滿足定義的正規表示式驗證檢查,瀏覽器必須在輸入字串中進行數以百萬計的回溯。如此多的回溯檢查可能會導致"災難性的回溯",這種現象是複雜的正規表示式會凍結瀏覽器或使CPU核心程式爆滿。
安全使用JavaScript
正如SSRF被新增到新的OWASP Top 10中所證明的那樣,URL驗證對於JavaScript應用程式的安全性已經變得越來越關鍵。幸運的是,我們可以透過在伺服器端驗證URL來幫助緩解此類攻擊。此外,根據驗證和處理URL的首選方式來使用new URL
函式會非常有益。
在看到new URL
函式的一些使用案例後,我們學習瞭如何用正規表示式驗證一個URL--並看到了為什麼這種方法很麻煩而且容易出錯。
URL的安全風險與其說是關於其有效性,不如說是關於危險的URL方案。因此,我們需要確保讓伺服器端的應用程式進行驗證。攻擊者可以繞過客戶端的驗證機制,所以僅僅依靠它並不是解決辦法。
以上就是本文的所有內容,如果對你有所幫助,歡迎點贊、收藏、轉發~