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ị hạn chế bởi cùng một chính sách nguồn gốc. Tập lệnh nội dung khởi tạo 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 theo cùng một chính sách nguồn gốc. Nguồn gốc của tiện ích không bị hạn chế quá nhiều. Tập lệnh thực thi trong 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 trên nhiều nguồn gốc.

Nguồn gốc của phần mở rộng

Mỗi tiện ích đang chạy đều tồn tại trong một nguồn gốc bảo mật riêng. Nếu không yêu cầu đặc quyền bổ sung, tiện ích có thể gọi fetch() để nhận 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, thì trong thư mục config_resources/, 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 ngoài chính nó, chẳng hạn như https://www.google.com, thì trình duyệt sẽ không cho phép tiện ích đó trừ phi tiện ích yêu cầu cấp 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 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 so khớp hoặc cả hai vào phần host_permissions của tệp tệp kê khai.

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

Giá trị của 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 chúng có thể là các mẫu trùng khớp, chẳng hạn như:

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

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

Ngoài ra, xin lưu ý rằng quyền truy cập do cả máy chủ lưu trữ và chương trình truy cập đều được cấp. Nếu muốn truy cập cả HTTP an toàn lẫn không bảo mật vào một máy chủ hoặc một nhóm máy chủ cụ thể, thì tiện ích phải khai báo riêng các quyền này:

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

Fetch() so với XMLHttpRequest()

fetch() được tạo riêng cho trình chạy dịch vụ và đi theo xu hướng web rộng hơn so với 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 được ưu tiên sử dụng fetch() bất cứ khi nào có thể.

Lưu ý về bảo mật

Tránh các lỗ hổng bảo mật tập lệnh trên nhiều trang web

Khi sử dụng tài nguyên được truy xuất qua fetch(), 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 của bạn phải cẩn thận để không trở thành nạn nhân của tập lệnh chéo trang web. Cụ thể, bạn nên 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 mà 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;

Giới hạn quyền truy cập vào tập lệnh nội dung đối với các yêu cầu có nguồn gốc khác

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

Hãy xem xét ví dụ trong đó một tiện ích thực hiện yêu cầu 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à 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 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 nhiều nguồn gốc.

Thay vào đó, hãy thiết kế các trình xử lý thông báo giới hạn các tài nguyên có thể tìm nạp. Bên dưới, chỉ có itemId là do tập lệnh nội dung cung cấp chứ không phải URL đầy đủ.

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 chú ý đến những 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 máy chủ, 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 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 của mình 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ủ mà bạn muốn kết nối đều được phép. Mặc dù chính sách mặc định không hạn chế việc kết nối với các máy chủ, nhưng hãy cẩn thận khi thêm lệnh connect-src hoặc default-src một cách rõ ràng.