Chúng ta sẽ có nhu cầu hooking vào các native function của JavaScript để xem các đối số truyền vào và giá trị trả về tương tự như cách mà Frida hoạt động.
Ví dụ, để hook vào hàm các event handler dành cho sự kiện message
và in ra dữ liệu truyền vào event handler thì ta có thể dùng đoạn script sau:
(() => {
const orig = window.addEventListener;
window.addEventListener = function(a, b) {
if (a === "message") {
console.log("postMessage handler found");
console.log(b); // You can click the output of this to go directly to the handler
console.trace(); // Find where the handler was registered.
}
return orig(...arguments);
}
})();
Về bản chất, chúng ta lưu lại hàm gốc (const orig = window.addEventListener
) rồi thay thế nó bằng một custom function có sử dụng console.log
. Cuối cùng, chúng ta trả về kết quả mà đáng lẽ ra sẽ được trả về bởi hàm gốc (return orig(...arguments)
).
Tuy nhiên, chúng ta cần hook các native function trước khi nó được sử dụng bởi ứng dụng và điều này đòi hỏi việc sử dụng object Proxy
của JavaScript. Theo tài liệu của MDN, đây là một object cho phép chúng ta tái định nghĩa các thao tác của một object chẳng hạn như thao tác truy xuất thuộc tính, gán giá trị cho thuộc tính, …
Object Proxy
có constructor nhận vào 2 đối số:
target
: là object gốchandler
: là một object giúp tái định nghĩa các method của các object bị intercepted.
Ví dụ:
const target = {
message1: "hello",
message2: "everyone",
};
const handler2 = {
get(target, prop, receiver) {
return "world";
},
};
const proxy2 = new Proxy(target, handler2);
Do handler2
tái định nghĩa get
của object thành một hàm luôn trả về "world"
nên ta sẽ có kết quả sau:
console.log(proxy2.message1); // world
console.log(proxy2.message2); // world
Để gọi lại hàm gốc, ta sẽ sử dụng object Reflect
:
const target = {
message1: "hello",
message2: "everyone",
};
const handler3 = {
get(target, prop, receiver) {
if (prop === "message2") {
return "world";
}
return Reflect.get(...arguments);
},
};
const proxy3 = new Proxy(target, handler3);
console.log(proxy3.message1); // hello
console.log(proxy3.message2); // world
Tận dụng điều này, ta có thể intercept các đối số truyền vào các dangerous sinks chẳng hạn như document.write
:
/*********************************************************
*** Your code goes goes here to run in pages scope ***
*********************************************************/
// example code to dump all arguments to document.write
document.write = new Proxy(document.write, {
apply: function(_func, _doc, args) {
console.group(`[**] document.write.apply arguments`);
for (const arg of args) {
console.dir(arg);
}
console.groupEnd();
return Reflect.apply(...arguments);
}
});
Extension swoops/eval_villain sử dụng cách tiếp cận trên cùng với Content Scripts của FireFox. Về content scripts, đây là những script thuộc về các extension và được phép truy cập trực tiếp vào DOM của web page, có khả năng chạy trong mọi frame con của trang và chạy trước khi các script khác của ứng dụng được load.
title: Một ví dụ khá hay của Gemini:
Đây là một điểm cực kỳ quan trọng về cách ly môi trường (sandboxing):
- **KHÔNG có quyền truy cập trực tiếp vào scope của trang (page's scope)**: Content script không thể đọc các biến JavaScript hoặc gọi các hàm do trang web đó định nghĩa. Ví dụ, nếu trang Facebook có một biến là `window.currentUser`, content script của bạn không thể đọc `window.currentUser` một cách trực tiếp. Chúng hoạt động trong hai "thế giới" JavaScript riêng biệt.
- **CÓ quyền truy cập trực tiếp vào DOM**: Tuy nhiên, cả content script và trang web đều chia sẻ chung một cấu trúc DOM. Điều này có nghĩa là content script có thể đọc, thay đổi, thêm, xóa bất kỳ phần tử HTML nào trên trang (ví dụ: tìm một nút bấm và đổi màu nó, hoặc thêm một `div` mới vào trang).
- **Ý nghĩa**: Content script giống như một người thợ được phép vào nhà bạn (trang web) để sửa sang, di chuyển đồ đạc (DOM), nhưng không được phép nghe các cuộc trò chuyện riêng tư của gia đình bạn (biến và hàm JavaScript).
Reference: [Gemini - Giải Thích Proxy và Reflect](https://gemini.google.com/share/744f8b91ddd2)
Content script sẽ được inject vào trang web vẫn chịu ảnh hưởng của [[Content Security Policy|CSP]] và ta có thể disable CSP của browser nếu cần thiết.
Extension Eval Villain không chỉ hiển thị input của sink mà nó còn chỉ ra injection point và decode giá trị:
Ngoài ra, nó còn cung cấp encoder function để ta có thể thử với nhiều payload khác nhau.
Đoạn sau của bài viết cung cấp cách hook vào các non-native JS function với việc tạo ra `Proxy` object cho các object cần theo dõi và inject vào client-side script dưới dạng một conditional debugger. Tuy nhiên, các đoạn code được đưa ra quá khó hiểu và thiếu ngữ cảnh cụ thể để có thể hiểu được phần này.
[Client-side JavaScript Instrumentation · Doyensec's Blog](https://blog.doyensec.com/2023/09/25/clientside-javascript-instrumentation.html)