血淋淋的事實告訴你:你為什麼不應該在JS檔案中儲存敏感資訊
在JavaScript檔案中儲存敏感資料,不僅是一種錯誤的實踐方式,而且還是一種非常危險的行為,長期以來大家都知道這一點。
而原因也非常簡單,我們可以假設你為你的使用者動態生成了一個包含API金鑰的JavaScript檔案:
apiCall= function(type, api_key, data) { ... } var api_key = '1391f6bd2f6fe8dcafb847e0615e5b29' var profileInfo = apiCall('getProfile', api_key, 'all')
跟上述例子一樣,每當你在全域性範圍建立一個變數,意味著網站中任何一部分程式碼都可以訪問到這個變數,包括你託管的其他指令碼在內。
為什麼這樣做很明顯是不安全的?
為什麼開發人員不應該在JavaScript檔案中嵌入敏感資訊呢?原因有很多,對於經驗不豐富的開發人員來說,通過JavaScript檔案來傳遞資料是一種非常簡單的方法,因為你可以將資料在伺服器端生成和儲存,然後將它們傳遞給客戶端程式碼,而且這樣還可以節省一部分發送給伺服器端的請求。但是,這種時候我們通常會忽略的一個因素就是瀏覽器的擴充套件外掛,有的時候為了使用相同的視窗物件,它們有的時候需要直接在DOM中注入script標籤,因為僅僅依靠內容指令碼可能無法實現預期的功能。
有沒有辦法保護變數的安全?
我們之前已經討論了全域性範圍了,對於瀏覽器中的JavaScript來說,一個全域性變數對於視窗物件來說是非常有用的。但是在ECMA Script5中,還有一種額外的範圍,也就是函式範圍。這也就意味著,如果我們使用var關鍵字在一個函式內部聲明瞭一個變數,它就不是全域性變量了。而在ECMA Script 6中又引入了另一種範圍,即塊範圍,這個範圍內的變數使用const和let關鍵字來宣告。
這兩種關鍵字可以用來宣告塊範圍中的變數,但是我們無法修改const變數的值、如果我們沒有用這些關鍵詞來宣告變數,或者說我們在函式外部使用了var變數,我們就相當於建立了一個全域性變數,而這種情況並不是我們經常想要出現的。
“use strict”
為了防止你不小心建立了全域性變數,其中一種有效方法就是啟用限制模式,大家可以在一個檔案或函式的起始位置新增字串“use strict”來實現這個功能。接下來,如果你之前沒有宣告這個變數的話,你將無法使用這個變數。
"use strict"; var test1 = 'arka' // 有效 test2= 'kapı' // 引用錯誤
我們可以在IIFE(立即呼叫的函式表示式)中使用這種技術,IIFE可以用來建立一個函式範圍,但是它們會立即執行函式主體,比如說:
(function(){ "use strict"; //在函式範圍內宣告變數 var privateVar = 'Secret value'; })() console.log(privateVar) // 引用錯誤
可能乍看過去這會是一種建立變數的有效方式(其內容無法在範圍外讀取),但其實不然。雖然IIFE是一種防止全域性名稱空間被幹擾的有效方式,但是它們並不能真正保護你的資料內容。
從私有變數中讀取敏感資料
實際上,我們幾乎無法保證私有變數中的內容真正的是“私有”的。原因有非常多,我們接下來會對其中的部分進行測試,雖然不夠完整,但也足夠證明給大家看,為什麼我們不能在JavaScript檔案中儲存敏感資料。
重寫原生函式
在下面的例子中,我們將使用一個api金鑰來向伺服器端傳送請求。因此,我們需要通過網路並以明文資料的形式傳送這個金鑰,而且現在在JavaScript中也沒有多少其他可選擇的方法。假設我們的程式碼使用了fetch()函式:
window.fetch= (url, options) => { console.log(`URL: ${url}, data:${options.body}`); }; //EXTERNAL SCRIPT START (function(){ "use strict"; var api_key ="1391f6bd2f6fe8dcafb847e0615e5b29" fetch('/api/v1/getusers', { method: "POST", body: "api_key=" + api_key }); })() //EXTERNAL SCRIPT END
你可以看到,我們可以直接重寫fetch()函式,然後竊取API金鑰。唯一的前提就是我們需要在我們的指令碼塊後include一個外部指令碼。在這個例子中,我們只是在控制檯console.log出來了這個API金鑰,但實際操作中我們還需要將其傳送到我們的伺服器中。
定義Setter和Getter
私有變數中可能不僅包含字串,還有可能包含物件或陣列。物件可以有不同的屬性,在多數情況下,你可以直接設定和讀取它們的值,但是JavaScript還支援很多其他有意思的功能。比如說,你可以在一個物件的屬性被設定或被訪問的時候執行另一個函式,這裡可以使用__defineSetter__和__defineGetter__函式來實現。如果我們在物件建構函式的原型中使用__defineSetter__函式,我們就可以輸出分配給目標物件屬性的所有值。
Object.prototype.__defineSetter__('api_key',function(value){ console.log(value); return this._api_key = value; }); Object.prototype.__defineGetter__('api_key',function(){ return this._api_key; }); //EXTERNAL SCRIPT START (function(){ "use strict" let options = {} options.api_key ="1391f6bd2f6fe8dcafb847e0615e5b29" options.name = "Alice" options.endpoint ="get_user_data" anotherAPICall(options); })() //EXTERNAL SCRIPT END
如果分配給物件屬性的是一個API金鑰,那我們就可以直接在setter中訪問它了。另一方面,getter也可以確保我們的後續程式碼能夠正確執行。
自定義列舉器
陣列肯定是不能忽略的一個因素,如果程式碼中使用了for迴圈來遍歷陣列值,我們就可以在陣列構造器原型中定義一個自定義的列舉器,這樣不僅可以允許我們訪問陣列中的內容,而且也不會影響原生函式的功能。
Array.prototype[Symbol.iterator]= function() { let arr = this; let index = 0; console.log(arr) return { next: function() { return { value: arr[index++], done: index > arr.length } } } }; //EXTERNAL SCRIPT START (function(){ let secretArray = ["this","contains", "an", "API", "key"]; for (let element of secretArray) { doSomething(element); } })() //EXTERNAL SCRIPT END
後話
除了本文所介紹的方法之外,攻擊者還有很多從JavaScript檔案中竊取敏感資訊的方法。有的情況下,使用IIFE、限制模式和在函式/塊範圍宣告變數都不一定能保證你的安全。因此,我建議大家可以從伺服器端動態獲取敏感資料,而不是直接在JavaScript檔案中儲存敏感資料。這樣不僅更加安全,而且還易於維護,何樂而不為?
* 參考來源: ofollow,noindex" target="_blank">arkakapimag ,FB小編Alpha_h4ck編譯,轉載請註明來自FreeBuf.COM