Yêu cầu mạng trên nhiều nguồn gốc

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 các trang web này bị giới hạn bởi chính sách về cùng một nguồn gốc. Tập lệnh nội dung khởi tạo các yêu cầu thay mặt cho nguồn gốc 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 thủ chính sách về cùng một nguồn gốc. Nguồn gốc của tiện ích không bị hạn chế như vậy. Tập lệnh thực thi trong một trình chạy dịch vụ tiện ích hoặc thẻ trên 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, miễn là tiện ích yêu cầu quyền từ nhiều nguồn.

Nguồn gốc của tiện ích

Mỗi tiện ích đang chạy tồn tại trong một 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 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 sử dụng một nguồn gốc bảo mật khác với chính tiện ích đó, chẳng hạn như https://www.google.com, thì trình duyệt sẽ không cho phép điều đó trừ phi tiện ích đã yêu cầu các quyền thích hợp trên nhiều nguồn gốc.

Yêu cầu quyền trên nhiều nguồn gốc

Để yêu cầu quyền truy cập vào máy chủ từ xa bên ngoài nguồn gốc của một tiện ích, hãy thêm máy chủ lưu trữ, so khớp mẫu hoặc cả hai vào phần host_permissions của tệp manifest.

{
  "name": "My extension",
  ...
  "host_permissions": [
    "https://www.google.com/"
  ],
  ...
}

Giá trị quyền trên nhiều nguồn gốc 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 đó có thể là mẫu khớp, như sau:

  • "https://*.google.com/"
  • "https://*/"

Mẫu khớp "https://*/" cho phép truy cập HTTPS vào tất cả miền có thể truy cập. Xin lưu ý rằng ở đây, các mẫu khớp tương tự như mẫu khớp tập lệnh nội dung, nhưng mọi thông tin đường dẫn theo sau máy chủ sẽ bị bỏ qua.

Ngoài ra, xin lưu ý rằng quyền truy cập được cấp cả theo máy chủ lưu trữ và theo lược đồ. Nếu một tiện ích 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, thì tiện ích đó phải khai báo các quyền riêng biệt:

"host_permissions": [
  "http://www.google.com/",
  "https://www.google.com/"
]

Tìm nạp() so với XMLHttpRequest()

fetch() được tạo riêng cho trình chạy dịch vụ và tuân theo một xu hướng web rộng lớn hơn, không còn sử dụng các hoạt động đồ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 lỗ hổng bảo mật tập lệnh trên nhiều trang web

Khi sử dụng các tài nguyên được truy xuất qua fetch(), bạn phải cẩn thận với tài liệu, bảng điều khiển bên hoặc cửa sổ bật lên ngoài màn hình để không trở thành nạn nhân của tính năng 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 vào các yêu cầu từ nhiều nguồn gốc

Khi thay mặt tập lệnh nội dung thực hiện các yêu cầu trên nhiều nguồn gốc, hãy cẩn thận phòng tránh các trang web độc hại có thể cố gắng mạo danh tập lệnh nội dung. Cụ thể, không cho phép các tập lệnh nội dung yêu cầu một URL tuỳ ý.

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 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 an toàn lắm là yêu cầu tập lệnh nội dung chỉ định tài nguyên chính xác 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 các 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 gốc.

Thay vào đó, hãy thiết kế trình xử lý thông báo giới hạn các tài nguyên có thể được tìm nạp. Dưới đây, chỉ có itemId do tập lệnh nội dung cung cấp, 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 sử dụng HTTPS thay vì 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 sử dụng trên một mạng không an toàn, 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 với máy chủ lưu trữ, nhưng hãy cẩn thận khi thêm rõ ràng lệnh connect-src hoặc default-src.