本系列文章所討論的 JavaScript 資安與逆向工程技術,旨在分享知識、探討防禦之道,並促進技術交流。
所有內容僅供學術研究與學習,請勿用於任何非法或不道德的行為。
讀者應對自己的行為負完全責任。尊重法律與道德規範是所有技術人員應共同遵守的準則。
本文同步發佈:https://nicklabs.cc/object-defineproperty-proxy
在前端資安與逆向工程中,若我們想觀察某個對象的行為(例如:監控某函式是否被呼叫、攔截特定 API 的回傳值),Object.defineProperty 與 Proxy 是最常見的手法。
這兩者不僅能幫助逆向工程師理解程式邏輯,同時也常被惡意攻擊者利用來竄改行為。
假設我們已經知道 token 是登入成功後儲存憑證的關鍵變數,那麼接下來就需要追蹤 token 在程式執行的哪個階段被賦值。
這時候就能透過 Object.defineProperty 來攔截並觀察 token 的變化。
需要注意的是Object.defineProperty 必須在目標變數被定義之前就執行,這樣才能成功攔截。
例如:可以在網頁剛開始載入 Script 時透過斷點暫停並執行Object.defineProperty,確保能捕捉到完整的變更過程。
若不方便手動操作,也可以利用 Tampermonkey 這類瀏覽器腳本工具,自動在頁面載入初期注入攔截程式碼,達到相同效果。
var __token = "";
Object.defineProperty(window, "token", {
get() {
console.log("有人讀取了 token");
return __token;
},
set(value) {
console.log("有人修改了 token:", value);
__token = value;
}
});
透過上圖可以看到只要是對token進行getter或是setter都可以在console中看到訊息提示。
Proxy 是 ES6 引入的強大 API,可以攔截物件的操作包含讀取、修改、呼叫、刪除...等等。
相比 Object.defineProperty 只能針對單一屬性Proxy能做到全域監控。
let user = { token: "" };
user = new Proxy(user, {
get(target, prop, receiver) {
console.log("有人讀取了:", prop);
return Reflect.get(target, prop, receiver);
},
set(target, prop, value, receiver) {
console.log("有人修改了:", prop, "=", value);
return Reflect.set(target, prop, value, receiver);
}
});
精準控制:可以針對物件的特定屬性進行精確的讀取和寫入監控,非常適合只需要關注一兩個關鍵屬性的情境。
向下相容性:作為 ES5 的標準在較舊的JavaScript環境中也能運作,這對於需要支援舊版瀏覽器的專案來說是個優勢。
無法全面監控:必須為每一個需要監控的屬性單獨呼叫 Object.defineProperty,對於像 window 這樣擁有大量屬性的物件來說操作起來會非常繁瑣。
無法監控動態新增的屬性:如果有腳本在執行時動態新增了屬性,Object.defineProperty 無法自動監控到這些新屬性。
全面代理:可以代理整個物件監控所有對其屬性的讀取、寫入、刪除,甚至是函式呼叫,這大大簡化了程式碼。
監控動態新增的屬性:它能自然地處理新增加到物件中的屬性,無需額外設定。
更多的攔截能力:除了讀取與寫入之外還能攔截其他操作,例如deleteProperty或apply,使其在監控上更具彈性。
新標準:是ES6的功能現代瀏覽器都已支援,但在一些老舊環境中可能無法使用。
性能考量:在絕大多數情況下差異不明顯,但在高頻率操作中性能開銷可能會比直接操作物件來得更高一些。