JavaScript已經(jīng)成為現(xiàn)代Web瀏覽器開(kāi)發(fā)中最普遍的技術(shù)之一。之前在《PTES-信息收集》中講到源代碼審計(jì)也可能獲取到一些敏感信息,但有的測(cè)試人員只審計(jì)html源代碼,而忽略審計(jì)js文件里的源代碼,今天我們就來(lái)講一講審計(jì)js文件都能獲取到哪些信息。
通過(guò)審計(jì)js文件源代碼能獲取到的信息,我個(gè)人將其分為以下四類(lèi):敏感信息、業(yè)務(wù)邏輯、加密算法、Ajax請(qǐng)求。
敏感信息
審計(jì)js文件最直觀的就是很有可能獲取到賬號(hào)密碼、配置等敏感信息。
HTML定義頁(yè)面結(jié)構(gòu),CSS定義裝修樣式,而JavaScript定義前端工作內(nèi)容,主要是監(jiān)聽(tīng)事件執(zhí)行動(dòng)作,比如邏輯判斷,亦或是向服務(wù)器發(fā)送請(qǐng)求等等。而要正確執(zhí)行這些動(dòng)作,通常都會(huì)進(jìn)行一些簡(jiǎn)單的配置。
最常見(jiàn)的配置就是設(shè)置網(wǎng)站根路徑,不過(guò)也經(jīng)常遇到直接在js文件里配置賬號(hào)密碼的情況;更有甚者,有的開(kāi)發(fā)人員直接在js文件里定義SQL語(yǔ)句,然后由前端發(fā)送到服務(wù)器執(zhí)行。這不僅泄露了數(shù)據(jù)庫(kù)信息,也提高了SQL注入漏洞攻擊的風(fēng)險(xiǎn)。
以上是獲取敏感信息的一種情況,還有一種情況就是注釋。
為了更好的維護(hù)代碼、調(diào)試代碼,我們經(jīng)常在源碼中見(jiàn)到各種注釋。比如為了方便調(diào)試,直接把賬號(hào)密碼寫(xiě)在Javascript代碼中,調(diào)試完后,只是把這條語(yǔ)句注釋掉而非刪掉;有時(shí)候系統(tǒng)更迭,推出一些新的接口,開(kāi)發(fā)人員把老接口注釋掉使用新的接口,但服務(wù)器端并沒(méi)有把老接口關(guān)掉等等。
以上情況不僅讓我們搜集到了更多目標(biāo)信息,也增加了系統(tǒng)攻擊面,只能說(shuō):這些開(kāi)發(fā)人員真他娘的是個(gè)人才。
業(yè)務(wù)邏輯
除了能獲取到敏感信息,審計(jì)js文件還可以獲取到Web系統(tǒng)的一些業(yè)務(wù)邏輯。
說(shuō)到業(yè)務(wù)邏輯,就要牽扯到前端校驗(yàn)。有些系統(tǒng)只做了前端校驗(yàn),只要響應(yīng)包滿(mǎn)足條件,就會(huì)執(zhí)行下一步操作,服務(wù)器端不會(huì)再進(jìn)一步檢驗(yàn)接下來(lái)的請(qǐng)求是否有問(wèn)題,我們只要修改響應(yīng)包就可以繞過(guò)檢測(cè)。
比如輸入錯(cuò)誤的賬號(hào)密碼,響應(yīng)false,要求重新輸入賬號(hào)密碼;我們把響應(yīng)包攔截,把false改成true,前端檢測(cè)到true,認(rèn)為賬號(hào)密碼正確,發(fā)送跳轉(zhuǎn)到系統(tǒng)內(nèi)部的請(qǐng)求,而服務(wù)器沒(méi)有再校驗(yàn)請(qǐng)求是否有問(wèn)題,將系統(tǒng)內(nèi)部頁(yè)面響應(yīng)給前端。
要繞過(guò)前端校驗(yàn),像0/1、true/false這種很容易就能猜到該怎么修改,但有些系統(tǒng)可能有十多個(gè)響應(yīng)碼,或者響應(yīng)碼有多位,如果我們耐著性子一個(gè)一個(gè)去試,時(shí)間成本會(huì)很大。
比如0是用戶(hù)名錯(cuò)誤,1是密碼錯(cuò)誤,2是驗(yàn)證碼錯(cuò)誤,9999是登陸成功,如果我們從0開(kāi)始,一個(gè)一個(gè)去試,說(shuō)真的還不一定能試出來(lái)。除非先輸入正確的賬號(hào)密碼得到正確的響應(yīng)碼,然后輸入錯(cuò)誤的賬號(hào)密碼,再用之前得到的正確響應(yīng)碼替換錯(cuò)誤的響應(yīng)碼,嘗試看是否存在前端校驗(yàn)的問(wèn)題。
問(wèn)題是:如果是安全服務(wù),客戶(hù)還有可能提供賬號(hào)密碼,但攻防演練、挖洞一般都是靠自己白手起家,哪里有什么賬號(hào)密碼給你。而前端業(yè)務(wù)邏輯通常都由js實(shí)現(xiàn),基本都可以通過(guò)代碼審計(jì)來(lái)確定如何構(gòu)造響應(yīng)包來(lái)嘗試?yán)@過(guò)前端校驗(yàn)。我只想說(shuō):信息收集真的很重要。
加密算法
審計(jì)代碼能獲取到的第三類(lèi)信息就是加密算法。在當(dāng)今的大環(huán)境下,企業(yè)越來(lái)越重視安全,運(yùn)用了各種防護(hù)手段來(lái)提高系統(tǒng)的安全系數(shù),提高攻擊者的攻擊成本,其中就有加密機(jī)制。
可能很多測(cè)試人員在測(cè)試的時(shí)候,抓包發(fā)現(xiàn)參數(shù)值都是加密的,就這么放過(guò)了。但這種前端加密都是可以在js中找到加密算法的,要么在html文件的js中,要么包含在js文件中。
當(dāng)然我們也可以不需要知道加密算法到底是怎么樣的,直接輸入測(cè)試的明文Payload,系統(tǒng)自然會(huì)利用加密算法輸出對(duì)應(yīng)的加密數(shù)據(jù)。這個(gè)Payload不行,我們?cè)佥斎胂乱粋€(gè)明文Payload去測(cè)試。
以上的方式時(shí)間成本會(huì)增加很多,而且有時(shí)候系統(tǒng)會(huì)先檢測(cè)輸入的數(shù)據(jù)是否含有非法字符,沒(méi)有非法字符才會(huì)加密發(fā)送到服務(wù)器端。而只要我們找到了對(duì)應(yīng)的加密算法,就可以手工構(gòu)造或者編寫(xiě)腳本批量生成我們想要的加密數(shù)據(jù),進(jìn)而去測(cè)試系統(tǒng)是否存在漏洞。
據(jù)目前工作經(jīng)歷來(lái)看,涉及到加密的參數(shù),存在漏洞的幾率要高很多。因?yàn)榍岸思用芎螅蠖吮仨毥饷懿拍苁褂谩5芏鄷r(shí)候,系統(tǒng)的安全檢測(cè)只在請(qǐng)求傳過(guò)來(lái)的時(shí)候,加密時(shí)沒(méi)有檢測(cè)到非法字符就繞過(guò)了檢測(cè),后端解密之后就可以肆意橫行。
Ajax請(qǐng)求
審計(jì)js文件能獲取到的信息還有Ajax請(qǐng)求,也是漏洞重災(zāi)區(qū)。而Ajax請(qǐng)求的調(diào)用通常很隱蔽,且Ajax請(qǐng)求的觸發(fā)條件無(wú)規(guī)律,很容易被遺漏或忽略。
Ajax請(qǐng)求通常是被各個(gè)事件觸發(fā)調(diào)用,而這些事件的觸發(fā)往往需要滿(mǎn)足一定條件。但很多時(shí)候,即便事件觸發(fā)條件未滿(mǎn)足,我們?nèi)?gòu)造Ajax請(qǐng)求的數(shù)據(jù)包發(fā)送給服務(wù)器端,依然可以得到服務(wù)器的響應(yīng)。
服務(wù)器訪(fǎng)問(wèn)控制不當(dāng),并沒(méi)有去檢測(cè)用戶(hù)是否是通過(guò)正常業(yè)務(wù)邏輯發(fā)送Ajax請(qǐng)求,也沒(méi)有去檢測(cè)用戶(hù)是否有操作權(quán)限。服務(wù)器認(rèn)為客戶(hù)端發(fā)過(guò)來(lái)的請(qǐng)求都是正確的,這又要提一下安全的本質(zhì):一切輸入都是不安全的。而且每一個(gè)Ajax請(qǐng)求都對(duì)應(yīng)一個(gè)接口,這又增加了攻擊面,每個(gè)請(qǐng)求都可能形成獨(dú)立的攻擊過(guò)程。
舉個(gè)真實(shí)案例:修改密碼,先要通過(guò)短信驗(yàn)證碼校驗(yàn)才能修改新密碼。只有管理員才能修改密碼,修改密碼的Ajax請(qǐng)求由修改密碼功能調(diào)用,普通用戶(hù)沒(méi)有這個(gè)功能。(某些原因,沒(méi)有截圖,意淫一下吧)
開(kāi)發(fā)人員認(rèn)為所有的用戶(hù)都會(huì)按照系統(tǒng)業(yè)務(wù)邏輯一步一步觸發(fā)請(qǐng)求,先點(diǎn)擊發(fā)送驗(yàn)證碼,然后填寫(xiě)驗(yàn)證碼進(jìn)行校驗(yàn),通過(guò)校驗(yàn)后跳轉(zhuǎn)到修改密碼的頁(yè)面,輸入新密碼,最后修改成功。而且只有管理員有這個(gè)功能頁(yè)面。
但通過(guò)代碼審計(jì),直接在js文件中找到了修改密碼的Ajax請(qǐng)求,請(qǐng)求參數(shù)只有新密碼。現(xiàn)在我們已經(jīng)找到了修改密碼的請(qǐng)求,那么我們可以跳過(guò)前面一系列的校驗(yàn)和請(qǐng)求,只需要構(gòu)造出修改密碼請(qǐng)求,就可以修改密碼。
而且這個(gè)Ajax請(qǐng)求是寫(xiě)在js文件里的,系統(tǒng)并不會(huì)因?yàn)橛脩?hù)權(quán)限的不同而調(diào)用不同的js文件,所以普通用戶(hù)也能夠查到到對(duì)應(yīng)的js代碼,不存在管理員一說(shuō)。
本文簡(jiǎn)單講述了審計(jì)js文件源代碼能獲取到的四類(lèi)信息:敏感信息、業(yè)務(wù)邏輯、加密算法、Ajax請(qǐng)求。
當(dāng)然,這也僅僅是我個(gè)人的歸類(lèi),審計(jì)代碼能獲取到的信息遠(yuǎn)不止這些,需要大家去深入挖掘,我純碎起一個(gè)拋磚引玉的作用。
忙里偷閑,差不多半年才寫(xiě)了一篇文章。這波行動(dòng)差不多要結(jié)束了,接下來(lái)就是國(guó)慶重保。國(guó)慶重保后不出意外的話(huà)應(yīng)該,可能,也許會(huì)稍微輕松一點(diǎn),爭(zhēng)取一個(gè)月能寫(xiě)篇文章吧。感謝大家的關(guān)注!
在之前的一篇文章《前端開(kāi)發(fā)過(guò)程中的HTML規(guī)范,來(lái)學(xué)習(xí)一下吧》中,我們有講過(guò)前端開(kāi)發(fā)過(guò)程中需要注意到的HTML規(guī)范問(wèn)題,今天這篇文章我們繼續(xù)來(lái)看下關(guān)于Javascript的規(guī)范問(wèn)題。
Javascript
我們總是會(huì)在Javascript文件中定義變量,但是一不留神就會(huì)將其定義成全局變量,如果引用的外部JS文件較多,很容易出現(xiàn)全局變量污染的情況。
我們不推薦總是在全局空間定義變量的行為,因?yàn)樗性谌挚臻g定義的變量都是掛在window對(duì)象上,很容易出現(xiàn)變量污染,如下所示。
不推薦-全局變量
IIFE就可以防止出現(xiàn)全局變量污染的情況,IIFE是立即執(zhí)行的函數(shù)表達(dá)式,在IIFE內(nèi)部會(huì)創(chuàng)建一個(gè)封閉的作用域,內(nèi)部定義的變量不會(huì)影響外部的執(zhí)行環(huán)境,而且可以通過(guò)參數(shù)傳遞的形式引用外部變量,最重要的一點(diǎn)是在函數(shù)執(zhí)行完后會(huì)立即釋放占用的內(nèi)存。
我們推薦使用下面這種寫(xiě)法。
推薦寫(xiě)法-IIFE
為了避免全局變量的干擾,我們建議所有腳本文件都從IIFE開(kāi)始。
我們都知道之所以叫立即執(zhí)行的函數(shù)表達(dá)式,是因?yàn)樵诤瘮?shù)表達(dá)式后面會(huì)多一個(gè)執(zhí)行的括號(hào)。這個(gè)執(zhí)行的括號(hào)可以出現(xiàn)在兩個(gè)地方,不管是在內(nèi)部還是外部,都是有效的。但是為了讓整個(gè)函數(shù)表達(dá)式看起來(lái)像一個(gè)整體,我們推薦將括號(hào)寫(xiě)在里面。
因此我們不推薦以下寫(xiě)法。
不推薦寫(xiě)法
而推薦以下寫(xiě)法。
推薦寫(xiě)法
同樣,我們可以通過(guò)參數(shù)傳遞的形式引用外部變量。
引用外部變量
我們都知道在ES5中是沒(méi)有塊級(jí)作用域概念的,只有函數(shù)級(jí)作用域,而且由于變量提升的存在,在函數(shù)內(nèi)部聲明的變量都會(huì)提升至函數(shù)頂部,這就會(huì)造成一些難以預(yù)料的問(wèn)題。
首先我們來(lái)看看變量提升是什么樣的情況?看看下面一段代碼。
變量提升
上面這段代碼返回的結(jié)果是undefined,并不是'Hello Shenzhen',這是因?yàn)樽兞縱會(huì)在函數(shù)內(nèi)部被提升至函數(shù)頂部,實(shí)際執(zhí)行的其實(shí)是下面這段代碼。
實(shí)際執(zhí)行
為了降低變量提升所帶來(lái)的編碼風(fēng)險(xiǎn),我們應(yīng)該手動(dòng)聲明定義的變量和方法,并把其放在函數(shù)頂部。
我們不推薦以下寫(xiě)法。
不推薦寫(xiě)法
我們推薦以下寫(xiě)法。
推薦寫(xiě)法
在編寫(xiě)判斷相等類(lèi)型的條件語(yǔ)句時(shí),總是使用嚴(yán)格相等(===),這樣可以避免Javascript在執(zhí)行類(lèi)型轉(zhuǎn)換時(shí)帶來(lái)的問(wèn)題。
我們看下面一個(gè)例子,定義一個(gè)函數(shù),傳入一個(gè)數(shù)字,如果等于5,則將這個(gè)數(shù)加5返回。如果不使用嚴(yán)格等于,在傳入一個(gè)字符串'5'后,會(huì)返回'55'。
沒(méi)有使用嚴(yán)格相等
因此,我們推薦在使用相等判斷時(shí)都采用嚴(yán)格相等(===)。
強(qiáng)烈建議在所有結(jié)束語(yǔ)句后面加上分號(hào),如果不加上分號(hào)會(huì)引起一些很難發(fā)現(xiàn)的問(wèn)題。我們看看下面一段代碼。
代碼
在上面這段代碼執(zhí)行后,我們發(fā)現(xiàn)即使resultOperation()函數(shù)返回-1,與-1相等,后面的method方法仍然被調(diào)用。
這是因?yàn)樵谏厦娑x的數(shù)組末尾沒(méi)有加上分號(hào),這個(gè)數(shù)組會(huì)與下面一行的-1當(dāng)做表達(dá)式執(zhí)行,任何非空數(shù)組-1都會(huì)返回NaN,NaN與resultOperation的返回結(jié)果-1不相等,因此后面的method方法會(huì)被執(zhí)行。
省略分號(hào)不寫(xiě),不只是會(huì)出現(xiàn)上述的問(wèn)題,還有很多,這里不一一列舉。
因此,建議在每個(gè)結(jié)尾的語(yǔ)句后加上分號(hào),養(yǎng)成一個(gè)好的習(xí)慣。
閉包作為前端面試題中必不可少的知識(shí)點(diǎn)是應(yīng)該要掌握的,而且在前端開(kāi)發(fā)中經(jīng)常會(huì)涉及到,關(guān)于閉包的問(wèn)題,在我寫(xiě)的一篇文章《前端面試中不可逃避的閉包問(wèn)題,你真的了解嗎?》中有詳細(xì)介紹,大家可以好好看下。
今天這篇文章詳細(xì)的介紹了在前端開(kāi)發(fā)過(guò)程中涉及到的Javascript規(guī)范問(wèn)題,可能還不夠全面,大家可以自行補(bǔ)充。
前端項(xiàng)目開(kāi)發(fā)的時(shí)候,系統(tǒng)支持文件下載是前端開(kāi)發(fā)中常用到的功能之一,當(dāng)用戶(hù)訪(fǎng)問(wèn)我們的網(wǎng)站時(shí)發(fā)現(xiàn)有自己需要的資源時(shí)可以將資源下載下來(lái)。文件下載主要有兩種形式,一種是通過(guò)文件地址下載,該文件可以存放在前端或者后端。另一種則是通過(guò)文件流下載,前端通過(guò)發(fā)送請(qǐng)求給后端并獲取后端文件流進(jìn)行下載。
download屬性:是HTML5中的a標(biāo)簽的新特性,用來(lái)規(guī)定被下載的超鏈接目標(biāo)。在a標(biāo)簽中如果沒(méi)有申明download屬性的時(shí)a標(biāo)簽的默會(huì)鏈接跳轉(zhuǎn)進(jìn)行預(yù)覽(如txt , jpg , pdf ),當(dāng)前瀏覽器不支持預(yù)覽的文件時(shí)則出現(xiàn)下載。當(dāng)申明了download屬性之后瀏覽器會(huì)對(duì)href屬性鏈接的文件進(jìn)行下載。download屬性與不支持H5的低版本瀏覽器不兼容且僅限于同源文件,如果是非同源download屬性會(huì)失效。比如引用第三方的網(wǎng)站內(nèi)容、引用前后端分離的服務(wù)器內(nèi)容、甚至本地測(cè)試引用的本地文件,download都會(huì)不起作用。如果你想測(cè)試該功能可以在本地開(kāi)一個(gè)服務(wù),將文件放同一服務(wù)中測(cè)試就可以了。
直接使用a標(biāo)簽時(shí)只要在a標(biāo)簽中添加download屬性,如果是非a標(biāo)簽的話(huà)可以在出發(fā)事件的時(shí)候通過(guò)JavaScript來(lái)創(chuàng)建一個(gè)隱藏a標(biāo)簽下載,當(dāng)我們點(diǎn)擊時(shí)觸發(fā)隱藏的a標(biāo)簽下載事件。這里使用appendChild和removeChild的處理是為了兼容Firefox瀏覽器。
需要了解XMLHttpRequest可以參考文章:JavaScript實(shí)戰(zhàn)001:XMLHttpRequest使用入門(mén),這里我利用XMLHttpRequest對(duì)象來(lái)發(fā)送請(qǐng)求,用blob類(lèi)型來(lái)接受后臺(tái)發(fā)過(guò)來(lái)的二進(jìn)制類(lèi)型文件。然后模擬a標(biāo)簽創(chuàng)建隱藏的下載鏈接,通過(guò)URL.createObjectURL()方法創(chuàng)建一個(gè)指向blob對(duì)象的URL地址。
iframe是HTML標(biāo)簽元素,可以用來(lái)創(chuàng)建內(nèi)聯(lián)框架。iframe提供了src屬性用來(lái)規(guī)定在 iframe 中顯示的文檔的 URL,我們可以直接將文件地址拋給iframe,也可以賦值文件流地址給iframe。功能實(shí)現(xiàn)跟a標(biāo)簽有點(diǎn)相似,創(chuàng)建一個(gè)隱藏的iframe標(biāo)簽來(lái)實(shí)現(xiàn)文件的下載功能。使用文件地址下載需要注意的是瀏覽器支持預(yù)覽的文件類(lèi)型無(wú)法下載(比如圖片、PDF文檔、text文本等會(huì)直接打開(kāi)文件預(yù)覽),文件流下載只需將請(qǐng)求接口賦給src屬性,iframe會(huì)自動(dòng)去請(qǐng)求該文件實(shí)現(xiàn)下載。
window.open()方法用于打開(kāi)一個(gè)新的瀏覽器窗口或查找一個(gè)已命名的窗口,也可以用它來(lái)實(shí)現(xiàn)文件下載功能。而且這個(gè)比iframe更簡(jiǎn)單,直接將文件地址或者請(qǐng)求接口賦給window.open(url)方法即可實(shí)現(xiàn)文件下載功能。但是有個(gè)缺點(diǎn)就是每次下載都會(huì)打開(kāi)一個(gè)新的窗口來(lái)實(shí)現(xiàn)下載(想不跳轉(zhuǎn)可以嘗試window.location.assign()方法,用于加載一個(gè)新的文檔),而且如果使用文件地址下載的是瀏覽器支持預(yù)覽的文件類(lèi)型無(wú)法下載(比如圖片、PDF文檔、text文本等會(huì)直接打開(kāi)文件預(yù)覽)。
form表單是個(gè)比較常用的html表簽,用戶(hù)提交用戶(hù)信息,比如常見(jiàn)的登錄、注冊(cè)界面大部分都是通過(guò)form表單進(jìn)行數(shù)據(jù)提交的。form表單所有要提交的數(shù)據(jù)都必須放在form標(biāo)簽中,method屬性定義提交的方法(有g(shù)et和post兩種提交方法),action屬性定義請(qǐng)求的地址。form標(biāo)簽中支持input、menus、textarea、fieldset、legend 和 label 等元素,通過(guò)submit向服務(wù)器提交數(shù)據(jù)。這里我創(chuàng)建了form表單和input框,input用于輸入請(qǐng)求的參數(shù),form用于提交數(shù)據(jù)請(qǐng)求。
這里提供下Django的后臺(tái)文件請(qǐng)求接口,以上文件請(qǐng)求都是基于該接口實(shí)現(xiàn)的。接收請(qǐng)求方法為GET,請(qǐng)求參數(shù)為id(數(shù)據(jù)庫(kù)存儲(chǔ)的文件id),采用FileResponse方式返回的文件流信息(具體實(shí)現(xiàn)功能可以參考文章:Django實(shí)戰(zhàn)013:各種文件下載功能實(shí)現(xiàn)詳解)。
下載的方式方法有很多,以上只是JavaScript中常見(jiàn)的幾種下載方式。其實(shí)用ajax或者axios也可以實(shí)現(xiàn)下載,但是萬(wàn)變不離其中,會(huì)JavaScript下載害怕不會(huì)別的么。以上下載方式個(gè)人覺(jué)得還是iframe比較簡(jiǎn)單方便,請(qǐng)求最好還是通過(guò)文件流來(lái)實(shí)現(xiàn),相對(duì)文件地址下載會(huì)更安全些。
更多前端技巧可以參考專(zhuān)欄:Vue實(shí)戰(zhàn)技巧
*請(qǐng)認(rèn)真填寫(xiě)需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。