詳解JavaScript是如何驗(yàn)證URL的
前言
當(dāng)開(kāi)發(fā)者需要為不同目的以不同形式處理URL時(shí),比如說(shuō)瀏覽器歷史導(dǎo)航,錨點(diǎn)目標(biāo),查詢參數(shù)等等,我們經(jīng)常會(huì)借助于JavaScript。然而,它的頻繁使用促使攻擊者利用其漏洞。這種被利用的風(fēng)險(xiǎn)是我們必須在我們的JavaScript應(yīng)用程序中實(shí)現(xiàn)URL驗(yàn)證的原因。
URL驗(yàn)證檢查URL是否遵循正確的URL語(yǔ)法,也就是每個(gè)URL必須具備的結(jié)構(gòu)。URL驗(yàn)證可以使我們的應(yīng)用程序免遭基于URL的漏洞,比如惡意腳本注入和服務(wù)器端請(qǐng)求偽造(SSRF)。當(dāng)我們?cè)讷@取遠(yuǎn)程資源時(shí)沒(méi)有應(yīng)用安全編碼慣例來(lái)驗(yàn)證用戶提供的URL時(shí),惡意行為者可以采用SSRF攻擊。
URL驗(yàn)證
URL驗(yàn)證的存在是為了加強(qiáng)安全,防止可能存在的漏洞,并消除運(yùn)行代碼時(shí)產(chǎn)生的任何錯(cuò)誤的機(jī)會(huì)。但是我們應(yīng)該在什么時(shí)候使用URL驗(yàn)證,在這個(gè)過(guò)程中我們要驗(yàn)證什么呢?我們應(yīng)該在所有必須識(shí)別和驗(yàn)證諸如網(wǎng)頁(yè)、圖片、gif和視頻等資源的軟件中實(shí)施URL驗(yàn)證。
一個(gè)典型的URL包括多個(gè)片段,比如協(xié)議、域名、主機(jī)名、資源名、URL源、端口等等。這些用來(lái)告訴瀏覽器如何追蹤指定的資源。我們可以以不同的方式來(lái)驗(yàn)證URL:
- 使用正則字面量和構(gòu)造函數(shù)
- URL構(gòu)造函數(shù)
isValidURL
方法- Input元素
- Anchor標(biāo)簽方法
一個(gè)典型的URL驗(yàn)證方案接收來(lái)自用戶的輸入,然后對(duì)其進(jìn)行解析,以識(shí)別其各個(gè)組成部分。驗(yàn)證方案可以確保所有的URL組件符合互聯(lián)網(wǎng)標(biāo)準(zhǔn)。例如,如果需要,它可以檢查URL是否使用安全協(xié)議。
主機(jī)名驗(yàn)證首先是將主機(jī)名分成獨(dú)立的標(biāo)簽,以確保它們符合頂級(jí)域名規(guī)范。一個(gè)典型的主機(jī)名由至少兩個(gè)用點(diǎn)分隔的標(biāo)簽組成。例如,www.snyk.com 有 "www"、"snyk"和 "com"的標(biāo)簽。每個(gè)標(biāo)簽只能由一個(gè)字母數(shù)字字符或一個(gè)連字符組成,無(wú)論大小寫(xiě)。然后,驗(yàn)證方案可以確保主機(jī)名與URL的允許列表相匹配,以確保只允許指定的URL,并且允許的URL不會(huì)被錯(cuò)誤地取消資格。
默認(rèn)情況下,URL中使用的大多數(shù)資源的路徑都是允許的。然而,端口只能在1到65536的范圍內(nèi)。任何超出這個(gè)范圍的東西都應(yīng)該拋出一個(gè)錯(cuò)誤。我們還可以檢查數(shù)字IP地址,以判斷它是一個(gè)IPV4地址還是IPV6地址。
最后,我們也可以檢查URL的用戶名和密碼。這個(gè)功能有助于遵守公司政策和憑證保護(hù)。
現(xiàn)在,你已經(jīng)有了這些基礎(chǔ)知識(shí),讓我們來(lái)看看使用javascript的URL驗(yàn)證吧。
如何執(zhí)行URL驗(yàn)證
在JavaScript中,執(zhí)行URL驗(yàn)證最簡(jiǎn)單的方式是使用new URL
構(gòu)造函數(shù)。除此之外,它還得到了Node.js運(yùn)行時(shí)和大多數(shù)瀏覽器的支持。
基本語(yǔ)法如下:
new URL (url) new URL (url , base)
如果提供相對(duì)URL,JavaScript只需要base
元素。如果不提供相對(duì)URL,默認(rèn)為undefined
。另外,如果提供一個(gè)具有絕對(duì)URL的base
元素,JavaScript會(huì)忽略base
元素。
為了驗(yàn)證URL,可以使用以下代碼:
function checkUrl (string) { let givenURL ; try { givenURL = new URL (string); } catch (error) { console.log ("error is", error); return false; } return true; }
該函數(shù)用于檢查URL的有效性。當(dāng)URL有效時(shí)返回true
,否則返回false
。
- 如果你傳遞
www.urlcheck.com
給該函數(shù)會(huì)返回false
。因?yàn)樵搮?shù)并不是一個(gè)有效的URL。正確版本應(yīng)該是https://urlcheck.com
。 - 另一個(gè)例子是
mailto:John.Doe@example.com
。這是一個(gè)有效的URL,但如果移除了冒號(hào),JavaScript就不再認(rèn)為它是一個(gè)URL了。 - 第三個(gè)例子是
ftp://
。這不是一個(gè)有效URL,因?yàn)闆](méi)有包含主機(jī)名。如果你添加兩個(gè)點(diǎn)(..
),就會(huì)變成有效URL。因?yàn)辄c(diǎn)會(huì)被認(rèn)為是一個(gè)主機(jī)名,也就是說(shuō)ftp://..
變成了一個(gè)有效的URL。
重要的是要記住,非常規(guī)的、但完全有效的URL是存在的!它們可能對(duì)從事這些工作的開(kāi)發(fā)人員來(lái)說(shuō)是意外的,但在其他方面是完全合適的。例如,以下兩個(gè)URL都會(huì)返回真值:
new URL("youtube://a.b.c.d");
new URL("a://1.2.3.4@1.2.3.4");
這些例子提醒我們,開(kāi)發(fā)者應(yīng)該依靠URL驗(yàn)證原則,而不是專注于慣例。
如果你想確保有效的URL包含一些特定的URL方案,你可以使用以下函數(shù):
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:"; }
該函數(shù)驗(yàn)證URL,然后檢查URL是否使用HTTP或者HTTPS。在這里,ftp://..
會(huì)被認(rèn)為是無(wú)效的,因?yàn)樗话琀TTP或者HTTPS,而http://..
依舊有效。
使用URL
構(gòu)造函數(shù)的一些其他方式包括:
let m = '<https://snyk.io>'; let a = new URL("/", m);
上述示例使用了base
元素。記錄下這個(gè)值,我們就可以得到https://snyk.io/
。
要返回一個(gè)URL對(duì)象而不指定base
參數(shù)的話,語(yǔ)法是:
let b = new URL(m);
為了給主機(jī)添加一個(gè)路徑名,我們的代碼結(jié)構(gòu)如下:
let d = new URL('/en-US/docs', b);
存儲(chǔ)在變量d
上的URL是https://snyk.io/en-US/docs
。
URL模塊的另一個(gè)功能是,它實(shí)現(xiàn)了WHATWG URL API,它遵守WHATWG的URL標(biāo)準(zhǔn),供瀏覽器使用:
let adr = new URL("<https://snyk.io/en-US/docs>"); let host = adr.host; let path = adr.pathname;
在上面的例子中,我們創(chuàng)建了一個(gè)名為adr
的URL對(duì)象。接著,代碼獲取URL的主機(jī)和路徑名,分別是snyk.io
和/en-US/docs
。最后,我們可以將URL和允許列表或者黑名單進(jìn)行對(duì)比,確保只有特定URL是被允許的。
如何使用正則驗(yàn)證
另一種驗(yàn)證URL的方法是使用正則表達(dá)式(regex)。我們可以使用Regex來(lái)檢查URL是否有效。
使用regex進(jìn)行URL驗(yàn)證的JavaScript語(yǔ)法是:
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); };
來(lái)測(cè)試一些URL:
var tc1 = "<http://helloworld.com>" console.log(isValidURL(tc1));
regex定義的URL語(yǔ)法檢查URL是否以http://
或https://
或子域開(kāi)始,以及是否包含域名??刂婆_(tái)上的語(yǔ)句結(jié)果是true
,因?yàn)樗裱擞蓃egex定義的URL語(yǔ)法。相反,下面的語(yǔ)句將返回一個(gè)false
,因?yàn)樗鼪](méi)有以任何允許的方案或子域開(kāi)始,也不包含域名:
var tc4 = "helloWorld"; console.log (isValidURL(tc4));
上面的正則表達(dá)式相對(duì)簡(jiǎn)單,但仍然難以駕馭。這也是一個(gè)容易出錯(cuò)的方法,因?yàn)橐粋€(gè)正則表達(dá)式不能充分處理驗(yàn)證URL的規(guī)則。它最多只能做到匹配有效的URL。此外,當(dāng)一個(gè)正則表達(dá)式要么包含復(fù)雜的驗(yàn)證邏輯,要么收到冗長(zhǎng)的輸入字符串時(shí),執(zhí)行驗(yàn)證檢查就變得很耗時(shí)。
為了滿足定義的正則表達(dá)式驗(yàn)證檢查,瀏覽器必須在輸入字符串中進(jìn)行數(shù)以百萬(wàn)計(jì)的回溯。如此多的回溯檢查可能會(huì)導(dǎo)致"災(zāi)難性的回溯",這種現(xiàn)象是復(fù)雜的正則表達(dá)式會(huì)凍結(jié)瀏覽器或使CPU核心進(jìn)程爆滿。
安全使用JavaScript
正如SSRF被添加到新的OWASP Top 10中所證明的那樣,URL驗(yàn)證對(duì)于JavaScript應(yīng)用程序的安全性已經(jīng)變得越來(lái)越關(guān)鍵。幸運(yùn)的是,我們可以通過(guò)在服務(wù)器端驗(yàn)證URL來(lái)幫助緩解此類攻擊。此外,根據(jù)驗(yàn)證和處理URL的首選方式來(lái)使用new URL
函數(shù)會(huì)非常有益。
在看到new URL
函數(shù)的一些使用案例后,我們學(xué)習(xí)了如何用正則表達(dá)式驗(yàn)證一個(gè)URL--并看到了為什么這種方法很麻煩而且容易出錯(cuò)。
URL的安全風(fēng)險(xiǎn)與其說(shuō)是關(guān)于其有效性,不如說(shuō)是關(guān)于危險(xiǎn)的URL方案。因此,我們需要確保讓服務(wù)器端的應(yīng)用程序進(jìn)行驗(yàn)證。攻擊者可以繞過(guò)客戶端的驗(yàn)證機(jī)制,所以僅僅依靠它并不是解決辦法。
本文譯自:https://snyk.io/blog/secure-javascript-url-validation/
作者:Mannan Tirmizi
到此這篇關(guān)于詳解JavaScript是如何驗(yàn)證URL的的文章就介紹到這了,更多相關(guān)JavaScript驗(yàn)證URL內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
selenium+java中用js來(lái)完成日期的修改
這篇文章主要介紹了selenium+java中用js來(lái)完成日期的修改,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-10-10淺析jsopn跨域請(qǐng)求原理及cors(跨域資源共享)的完美解決方法
由于同源策略的緣故,ajax不能向不同域的網(wǎng)站發(fā)出請(qǐng)求。接下來(lái)通過(guò)本文給大家介紹jsopn跨域請(qǐng)求原理及cors(跨域資源共享)的完美解決方法,需要的朋友可以參考下2017-02-02js實(shí)現(xiàn)帶緩動(dòng)動(dòng)畫(huà)的導(dǎo)航欄效果
本篇文章主要分享了js實(shí)現(xiàn)帶緩動(dòng)動(dòng)畫(huà)的導(dǎo)航欄效果的示例代碼。具有一定的參考價(jià)值,下面跟著小編一起來(lái)看下吧2017-01-01解決layui 表單元素radio不顯示渲染的問(wèn)題
今天小編就為大家分享一篇解決layui 表單元素radio不顯示渲染的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-09-09微信公眾號(hào)JS-SDK獲取當(dāng)前經(jīng)緯度以及地址信息的方法
最近微信JS-SDK開(kāi)發(fā)過(guò)程中,遇到了獲取坐標(biāo)位置的需求,所以下面這篇文章主要給大家介紹了關(guān)于微信公眾號(hào)JS-SDK獲取當(dāng)前經(jīng)緯度以及地址信息的相關(guān)資料,需要的朋友可以參考下2022-06-06bootstrap——bootstrapTable實(shí)現(xiàn)隱藏列的示例
本篇文章主要介紹了bootstrapTable實(shí)現(xiàn)隱藏列的示例,具有一定的參考價(jià)值,有興趣的可以了解一下。2017-01-01JavaScript中內(nèi)置函數(shù)Map()的使用
Map()是JavaScript中內(nèi)置的一種數(shù)據(jù)結(jié)構(gòu),它允許您將鍵值對(duì)映射到任意類型的值,主要介紹了JavaScript中內(nèi)置函數(shù)Map()的使用,感興趣的可以了解一下2023-05-05