Các trang web thông thường có thể sử dụng API fetch() hoặc XMLHttpRequest để gửi và nhận dữ liệu từ các máy chủ từ xa, nhưng bị giới hạn bởi chính sách cùng nguồn gốc. Tập lệnh nội dung khởi tạo các yêu cầu thay cho nguồn gốc trang web mà tập lệnh nội dung đã được chèn vào. Do đó, tập lệnh nội dung cũng phải tuân theo chính sách cùng nguồn gốc. Nguồn gốc của tiện ích không bị hạn chế nhiều như vậy. Một tập lệnh thực thi trong trình chạy dịch vụ tiện ích hoặc thẻ nền trước có thể giao tiếp với các máy chủ từ xa bên ngoài nguồn gốc của nó, miễn là tiện ích yêu cầu quyền truy cập vào máy chủ lưu trữ.
Nguồn gốc của tiện ích
Mỗi tiện ích đang chạy đều nằm trong nguồn gốc bảo mật riêng. Không cần yêu cầu thêm đặc quyền, tiện ích có thể gọi fetch() để lấy tài nguyên trong quá trình cài đặt. Ví dụ: nếu một tiện ích chứa tệp cấu hình JSON có tên là config.json, trong thư mục config_resources/, thì tiện ích đó có thể truy xuất nội dung của tệp như sau:
const response = await fetch('/config_resources/config.json');
const jsonData = await response.json();
Nếu tiện ích cố gắng yêu cầu nội dung từ một nguồn bảo mật khác với nguồn của chính tiện ích, chẳng hạn như https://www.google.com, thì yêu cầu này sẽ được coi là một yêu cầu trên nhiều nguồn gốc, trừ phi tiện ích có quyền truy cập vào máy chủ lưu trữ. Các yêu cầu trên nhiều nguồn gốc luôn được coi là như vậy trong tập lệnh nội dung, ngay cả khi tiện ích có quyền truy cập vào máy chủ lưu trữ.
Yêu cầu cấp quyền trên nhiều nguồn gốc
Để yêu cầu quyền truy cập vào các máy chủ từ xa bên ngoài nguồn gốc của tiện ích, hãy thêm máy chủ lưu trữ, mẫu khớp hoặc cả hai vào mục host_permissions của tệp manifest.
{
"name": "My extension",
...
"host_permissions": [
"https://www.google.com/"
],
...
}
Các giá trị quyền truy cập từ nhiều nguồn có thể là tên máy chủ đủ điều kiện, chẳng hạn như:
- "https://www.google.com/"
- "https://www.gmail.com/"
Hoặc chúng có thể là các mẫu so khớp, chẳng hạn như:
- "https://*.google.com/"
- "https://*/"
Mẫu so khớp "https://*/" cho phép truy cập HTTPS vào tất cả các miền có thể truy cập. Xin lưu ý rằng ở đây, các mẫu khớp tương tự như các mẫu khớp của tập lệnh nội dung, nhưng mọi thông tin về đường dẫn sau máy chủ đều bị bỏ qua.
Ngoài ra, xin lưu ý rằng quyền truy cập được cấp theo cả máy chủ và lược đồ. Nếu muốn có cả quyền truy cập HTTP an toàn và không an toàn vào một máy chủ hoặc nhóm máy chủ nhất định, tiện ích phải khai báo riêng các quyền:
"host_permissions": [
"http://www.google.com/",
"https://www.google.com/"
]
Fetch() so với XMLHttpRequest()
fetch() được tạo riêng cho các worker dịch vụ và tuân theo xu hướng chung của web là không dùng các thao tác đồng bộ. API XMLHttpRequest() được hỗ trợ trong các tiện ích bên ngoài trình chạy dịch vụ và việc gọi API này sẽ kích hoạt trình xử lý tìm nạp của trình chạy dịch vụ tiện ích. Công việc mới nên ưu tiên fetch() bất cứ khi nào có thể.
Lưu ý về bảo mật
Tránh các lỗ hổng tập lệnh trên nhiều trang web
Khi sử dụng các tài nguyên được truy xuất thông qua fetch(), tài liệu ngoài màn hình, bảng điều khiển bên hoặc cửa sổ bật lên của bạn phải cẩn thận để không trở thành nạn nhân của cross-site scripting (tập lệnh trên nhiều trang web). Cụ thể, hãy tránh sử dụng các API nguy hiểm như innerHTML. Ví dụ:
const response = await fetch("https://api.example.com/data.json");
const jsonData = await response.json();
// WARNING! Might be injecting a malicious script!
document.getElementById("resp").innerHTML = jsonData;
...
Thay vào đó, hãy ưu tiên các API an toàn hơn không chạy tập lệnh:
const response = await fetch("https://api.example.com/data.json");
const jsonData = await response.json();
// JSON.parse does not evaluate the attacker's scripts.
let resp = JSON.parse(jsonData);
const response = await fetch("https://api.example.com/data.json");
const jsonData = response.json();
// textContent does not let the attacker inject HTML elements.
document.getElementById("resp").textContent = jsonData;
Hạn chế quyền truy cập của tập lệnh nội dung đối với các yêu cầu từ nguồn khác
Khi thực hiện các yêu cầu trên nhiều nguồn thay cho một tập lệnh nội dung, hãy cẩn thận chống lại các trang web độc hại có thể cố gắng mạo danh một tập lệnh nội dung. Cụ thể, không cho phép tập lệnh nội dung yêu cầu một URL bất kỳ.
Hãy xem xét ví dụ về một tiện ích thực hiện yêu cầu trên nhiều nguồn gốc để cho phép một tập lệnh nội dung khám phá giá của một mặt hàng. Một phương pháp không mấy an toàn là để tập lệnh nội dung chỉ định chính xác tài nguyên mà trang nền sẽ tìm nạp.
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if (request.contentScriptQuery == 'fetchUrl') {
// WARNING: SECURITY PROBLEM - a malicious web page may abuse
// the message handler to get access to arbitrary cross-origin
// resources.
fetch(request.url)
.then(response => response.text())
.then(text => sendResponse(text))
.catch(error => ...)
return true; // Will respond asynchronously.
}
}
);
chrome.runtime.sendMessage(
{
contentScriptQuery: 'fetchUrl',
url: `https://another-site.com/price-query?itemId=${encodeURIComponent(request.itemId)}`
},
response => parsePrice(response.text())
);
Trong phương pháp trên, tập lệnh nội dung có thể yêu cầu tiện ích tìm nạp bất kỳ URL nào mà tiện ích có quyền truy cập. Một trang web độc hại có thể giả mạo những thông báo như vậy và lừa tiện ích cấp quyền truy cập vào các tài nguyên trên nhiều nguồn.
Thay vào đó, hãy thiết kế các trình xử lý thông báo để giới hạn những tài nguyên có thể tìm nạp. Ở bên dưới, chỉ có itemId được cung cấp bởi tập lệnh nội dung chứ không phải toàn bộ URL.
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if (request.contentScriptQuery == 'queryPrice') {
const url = `https://another-site.com/price-query?itemId=${encodeURIComponent(request.itemId)}`
fetch(url)
.then(response => response.text())
.then(text => parsePrice(text))
.then(price => sendResponse(price))
.catch(error => ...)
return true; // Will respond asynchronously.
}
}
);
chrome.runtime.sendMessage(
{contentScriptQuery: 'queryPrice', itemId: 12345},
price => ...
);
Ưu tiên HTTPS hơn HTTP
Ngoài ra, hãy đặc biệt cẩn thận với các tài nguyên được truy xuất qua HTTP. Nếu tiện ích của bạn được dùng trên một mạng thù địch, thì kẻ tấn công mạng (còn gọi là "man-in-the-middle") có thể sửa đổi phản hồi và có khả năng tấn công tiện ích của bạn. Thay vào đó, hãy ưu tiên sử dụng HTTPS bất cứ khi nào có thể.
Điều chỉnh chính sách bảo mật nội dung
Nếu sửa đổi Chính sách bảo mật nội dung mặc định cho tiện ích bằng cách thêm thuộc tính content_security_policy vào tệp kê khai, bạn cần đảm bảo rằng mọi máy chủ lưu trữ mà bạn muốn kết nối đều được cho phép. Mặc dù chính sách mặc định không hạn chế các kết nối đến máy chủ lưu trữ, nhưng hãy cẩn thận khi thêm rõ ràng chỉ thị connect-src hoặc default-src.