Las páginas web normales pueden usar las APIs de fetch()
o XMLHttpRequest
para enviar y recibir datos de servidores remotos, pero están limitadas por la política de mismo origen. Las secuencias de comandos de contenido inician solicitudes en nombre del origen web en el que se insertó la secuencia de comandos de contenido y, por lo tanto, también están sujetas a la política del mismo origen. Los orígenes de las extensiones no son tan limitados. Una secuencia de comandos que se ejecuta en un service worker de extensión o en una pestaña en primer plano puede comunicarse con servidores remotos fuera de su origen, siempre que la extensión solicite permisos de varios orígenes.
Origen de la extensión
Cada extensión en ejecución existe dentro de su propio origen de seguridad independiente. Sin solicitar privilegios adicionales, la extensión puede llamar a fetch()
para obtener recursos dentro de su instalación. Por
ejemplo, si una extensión contiene un archivo de configuración JSON llamado config.json
, en una
carpeta config_resources/
, la extensión puede recuperar el contenido del archivo de la siguiente manera:
const response = await fetch('/config_resources/config.json');
const jsonData = await response.json();
Si la extensión intenta usar un origen de seguridad que no sea el suyo, por ejemplo, https://www.google.com, el navegador no lo permite, a menos que la extensión haya solicitado los permisos de origen cruzado correspondientes.
Cómo solicitar permisos de origen cruzado
Para solicitar acceso a servidores remotos fuera del origen de una extensión, agrega hosts, patrones de coincidencia o ambos a la sección host_permissions del archivo manifest.
{
"name": "My extension",
...
"host_permissions": [
"https://www.google.com/"
],
...
}
Los valores de permisos de origen cruzado pueden ser nombres de host completamente calificados, como los siguientes:
- "https://www.google.com/"
- "https://www.gmail.com/"
También pueden ser patrones de coincidencia, como los siguientes:
- "https://*.google.com/"
- "https://*/"
Un patrón de coincidencia de “https://*/” permite el acceso HTTPS a todos los dominios accesibles. Ten en cuenta que, aquí, los patrones de coincidencia son similares a los patrones de coincidencia de la secuencia de comandos de contenido, pero se ignora cualquier información de ruta de acceso que siga al host.
Ten en cuenta también que el acceso se otorga por host y por esquema. Si una extensión desea tener acceso HTTP seguro y no seguro a un host o conjunto de hosts determinado, debe declarar los permisos por separado:
"host_permissions": [
"http://www.google.com/",
"https://www.google.com/"
]
Fetch() en comparación con XMLHttpRequest()
fetch()
se creó específicamente para los trabajadores del servicio y sigue una tendencia web más amplia que se aleja de las operaciones síncronas. La API de XMLHttpRequest()
es compatible con extensiones fuera del service worker, y llamarla activa el controlador de recuperación del service worker de la extensión. El trabajo nuevo debe favorecer fetch()
siempre que sea posible.
Consideraciones de seguridad
Evita las vulnerabilidades de secuencias de comandos entre sitios
Cuando uses recursos recuperados a través de fetch()
, tu documento fuera de la pantalla, el panel lateral o la ventana emergente deben tener cuidado de no ser víctimas de la ejecución de secuencias de comandos entre sitios. Específicamente, evita usar APIs peligrosas, como
innerHTML
. Por ejemplo:
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;
...
En su lugar, prefiere APIs más seguras que no ejecuten secuencias de comandos:
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;
Limita el acceso de la secuencia de comandos de contenido a las solicitudes de origen cruzado
Cuando realices solicitudes de origen cruzado en nombre de una secuencia de comandos de contenido, ten cuidado de protegerte contra páginas web maliciosas que puedan intentar suplantar la identidad de una secuencia de comandos de contenido. En particular, no permitas que las secuencias de comandos de contenido soliciten una URL arbitraria.
Considera un ejemplo en el que una extensión realiza una solicitud de origen cruzado para permitir que una secuencia de comandos de contenido descubra el precio de un artículo. Un enfoque no tan seguro sería hacer que la secuencia de comandos del contenido especifique el recurso exacto que recuperará la página en segundo plano.
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())
);
En el enfoque anterior, la secuencia de comandos del contenido puede pedirle a la extensión que recupere cualquier URL a la que esta tenga acceso. Una página web maliciosa puede falsificar esos mensajes y engañar a la extensión para que le otorgue acceso a recursos de varios orígenes.
En su lugar, diseña controladores de mensajes que limiten los recursos que se pueden recuperar. A continuación, solo la secuencia de comandos del contenido proporciona el itemId
, y no la URL completa.
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 => ...
);
Usa HTTPS en lugar de HTTP
Además, ten especial cuidado con los recursos recuperados a través de HTTP. Si tu extensión se usa en una red hostil, un atacante de red (también conocido como "man-in-the-middle") podría modificar la respuesta y, potencialmente, atacar tu extensión. En su lugar, prefiere HTTPS siempre que sea posible.
Ajusta la política de seguridad del contenido
Si modificas la Política de Seguridad del Contenido predeterminada de tu extensión agregando un atributo content_security_policy
al manifiesto, deberás asegurarte de que se permitan todos los hosts a los que te gustaría conectarte. Si bien la política predeterminada no restringe las conexiones a los hosts, ten cuidado cuando agregues de manera explícita las directivas connect-src
o default-src
.