跨域 (Cross-origin)XMLHttpRequest

警告

在 Manifest V3 中,后台页面不支持 XMLHttpRequest(由 Service Workers 提供)。请考虑使用其现代替代品 fetch()。

普通网页可以使用 XMLHttpRequest 对象从远程服务器发送和接收数据,但它们受到同源策略的限制(same origin policy)内容脚本(Content scripts)代表已注入内容脚本的 Web 源发起请求,因此内容脚本也受相同源策略的约束(same origin policy)。内容脚本从 Chrome 73 开始就受 CORB 约束,从 Chrome 83 开始受 CORS 约束。)扩展源不受限制 - 只要扩展请求跨源权限,在扩展的后台页面或前台选项卡中执行的脚本就可以与其源之外的远程服务器通信。

# Extension origin(扩展原点)

每个正在运行的扩展都存在于自己独立的安全源中。无需请求额外的权限,扩展可以使用 XMLHttpRequest 在其安装中获取资源。例如,如果扩展包含名为 config.json 的 JSON 配置文件,则在 config_resources 文件夹中,扩展可以像这样检索文件的内容:

var xhr = new XMLHttpRequest();
xhr.onreadystatechange = handleStateChange; // Implemented elsewhere.
xhr.open("GET", chrome.extension.getURL('/config_resources/config.json'), true);
xhr.send();

如果扩展程序尝试使用自身以外的安全源,例如 https://www.google.com,则浏览器会禁止它,除非扩展程序请求了适当的跨源权限。

# 请求跨域权限(Requesting cross-origin permissions)

通过将主机或主机匹配模式(或两者)添加到清单文件的 host_permissions 部分,扩展可以请求访问其来源之外的远程服务器。

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

跨域权限值可以是完全限定的主机名,如下所示:

或者它们可以是匹配模式,如下所示:

https://*/”的匹配模式允许 HTTPS 访问所有可访问的域。请注意,这里的匹配模式类似于内容脚本匹配模式,但忽略主机后面的任何路径信息。

另请注意,访问由主机和方案授予。如果扩展想要对给定主机或主机集进行安全和非安全 HTTP 访问,它必须单独声明权限:

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

# Security considerations(安全考虑)

# Avoiding cross-site scripting vulnerabilities(避免跨站脚本漏洞)

使用通过 XMLHttpRequest 检索的资源时,您的后台页面应该小心不要成为跨站点脚本(cross-site scripting)的牺牲品。具体来说,避免使用危险的 API,例如:

var xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/data.json", true);
xhr.onreadystatechange = function() {
  if (xhr.readyState == 4) {
    // WARNING! Might be evaluating an evil script!
    var resp = eval("(" + xhr.responseText + ")");
    ...
  }
}
xhr.send();
var xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/data.json", true);
xhr.onreadystatechange = function() {
  if (xhr.readyState == 4) {
    // WARNING! Might be injecting a malicious script!
    document.getElementById("resp").innerHTML = xhr.responseText;
    ...
  }
}
xhr.send();

相反,更喜欢不运行脚本的更安全的 API:

var xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/data.json", true);
xhr.onreadystatechange = function() {
  if (xhr.readyState == 4) {
    // JSON.parse does not evaluate the attacker's scripts.
    var resp = JSON.parse(xhr.responseText);
  }
}
xhr.send();
var xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/data.json", true);
xhr.onreadystatechange = function() {
  if (xhr.readyState == 4) {
    // textContent does not let the attacker inject HTML elements.
    document.getElementById("resp").textContent = xhr.responseText;
  }
}
xhr.send();

# Limiting content script access to cross-origin requests(限制对跨域请求的内容脚本访问)

代表内容脚本执行跨域请求时,请小心防范可能试图冒充内容脚本的恶意网页。特别是,不允许内容脚本请求任意 URL。

考虑一个示例,其中扩展程序执行跨域请求以让内容脚本发现商品的价格。一种(不安全的)方法是让内容脚本指定后台页面要获取的确切资源。

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()));

在上面的方法中,内容脚本可以要求扩展程序获取扩展程序可以访问的任何 URL。恶意网页可能会伪造此类消息并诱使扩展程序访问跨源资源。

相反,设计消息处理程序来限制可以获取的资源。下面,内容脚本仅提供 itemId,而不是完整的 URL。

chrome.runtime.onMessage.addListener(
    function(request, sender, sendResponse) {
      if (request.contentScriptQuery == 'queryPrice') {
        var 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 => ...);

# Preferring HTTPS over HTTP(首选 HTTPS 而非 HTTP)

此外,请特别注意通过 HTTP 检索的资源。如果您的扩展程序用于敌对网络,网络攻击者(又名“中间人("man-in-the-middle")”)可能会修改响应并可能攻击您的扩展程序。相反,尽可能选择 HTTPS。

# Adjusting the content security policy(调整内容安全策略)

如果您通过向清单添加 Content Security Policy 属性来修改扩展的默认内容安全策略,则需要确保允许您要连接的任何主机。虽然默认策略不限制与主机的连接,但在显式添加 connect-srcdefault-src 指令时要小心。

By.一粒技术服务

results matching ""

    No results matching ""