常规网页可以使用 XMLHttpRequest 对象从远程服务器发送和接收数据,但会受到同源政策的限制。内容脚本代表注入内容脚本的网站源发起请求,因此内容脚本也受同源政策的约束。(自 Chrome 73 起,内容脚本一直受 CORB 约束;自 Chrome 83 起,内容脚本一直受 CORS 约束。)扩展程序来源的限制并不那么严格 - 在扩展程序的后台页面或前台标签页中执行的脚本可以与来源之外的远程服务器通信,前提是扩展程序请求了跨源权限。
扩展程序来源
每个正在运行的扩展程序都存在于其自己的单独安全源中。无需请求其他权限,扩展程序即可使用 XMLHttpRequest 获取其安装中的资源。例如,如果扩展程序在 config_resources 文件夹中包含一个名为 config.json 的 JSON 配置文件,则该扩展程序可以按如下方式检索该文件的内容:
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),浏览器会禁止该操作,除非扩展程序已请求相应的跨源权限。
请求跨源权限
通过将主机或主机匹配模式(或两者)添加到清单文件的 permissions 部分,扩展程序可以请求访问其来源之外的远程服务器。
{
"name": "My extension",
...
"permissions": [
"https://www.google.com/"
],
...
}
跨源权限值可以是完全限定的主机名,例如:
- "https://www.google.com/"
- "https://www.gmail.com/"
也可以是匹配模式,例如:
- "https://*.google.com/"
- "https://*/"
匹配模式“https://*/”允许对所有可访问的网域进行 HTTPS 访问。请注意,此处的匹配模式与内容脚本匹配模式类似,但主机后面的任何路径信息都会被忽略。
另请注意,访问权限是按主机和按方案授予的。如果扩展程序需要对给定主机或一组主机进行安全和非安全 HTTP 访问,则必须分别声明相应权限:
"permissions": [
"http://www.google.com/",
"https://www.google.com/"
]
安全注意事项
避免跨站脚本攻击漏洞
使用通过 XMLHttpRequest 检索到的资源时,后台网页应注意不要成为跨站脚本攻击的受害者。具体来说,请避免使用以下危险 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,这些 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) {
// innerText does not let the attacker inject HTML elements.
document.getElementById("resp").innerText = xhr.responseText;
}
}
xhr.send();
限制内容脚本对跨源请求的访问权限
代表内容脚本执行跨源请求时,请务必防范可能试图冒充内容脚本的恶意网页。特别是,不允许内容脚本请求任意网址。
不妨考虑这样一个示例:扩展程序执行非同源请求,以使内容脚本能够发现商品的价格。一种(不安全)方法是让内容脚本指定要由后台网页提取的确切资源。
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()));
在上述方法中,内容脚本可以要求扩展程序提取扩展程序有权访问的任何网址。恶意网页可能能够伪造此类消息,并诱骗扩展程序授予对跨源资源的访问权限。
请设计可限制可提取资源的 message handler。在下例中,内容脚本仅提供 itemId,而非完整网址。
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 => ...);
优先选择 HTTPS(而非 HTTP)
此外,请特别注意通过 HTTP 检索到的资源。如果您的扩展程序在恶意网络上使用,网络攻击者(也称为“中间人”)可能会修改响应,并可能攻击您的扩展程序。"man-in-the-middle"请尽可能优先使用 HTTPS。
调整内容安全政策
如果您通过向清单添加 content_security_policy 属性来修改应用或扩展程序的默认内容安全政策,则需要确保允许连接到您想要连接的任何主机。虽然默认政策不会限制与主机的连接,但在明确添加 connect-src 或 default-src 指令时,请务必谨慎。