Kaynaklar arası XMLHttpRequest

Normal web sayfaları, uzak sunuculardan veri göndermek ve almak için XMLHttpRequest nesnesini kullanabilir ancak aynı kaynak politikası ile sınırlıdır. İçerik komut dosyaları, istekleri içerik komut dosyasının yerleştirildiği web kaynağı adına başlatır. Bu nedenle, içerik komut dosyaları da aynı kaynak politikasına tabidir. (İçerik komut dosyaları, Chrome 73'ten beri CORB'ye, Chrome 83'ten beri de CORS'ye tabidir.) Uzantı kaynakları o kadar sınırlı değildir. Bir uzantının arka plan sayfasında veya ön plan sekmesinde yürütülen bir komut dosyası, uzantı kaynaklar arası izinler istediği sürece kaynağının dışındaki uzak sunucularla iletişim kurabilir.

Uzantı kaynağı

Çalışan her uzantı, kendi güvenlik kaynağında bulunur. Uzantı, ek ayrıcalıklar istemeden XMLHttpRequest'i kullanarak yüklemesi içindeki kaynakları alabilir. Örneğin, bir uzantı config.json adlı bir JSON yapılandırma dosyası içeriyorsa ve bu dosya config_resources klasöründeyse uzantı, dosyanın içeriğini şu şekilde alabilir:

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

Uzantı, kendisi dışında bir güvenlik kaynağı (ör. https://www.google.com) kullanmaya çalışırsa tarayıcı, uzantı uygun kaynaklar arası izinleri istemediği sürece buna izin vermez.

Kaynaklar arası izin isteme

Uzantı, manifest dosyasının izinler bölümüne ana makineler veya ana makine eşleşme kalıpları (ya da her ikisi) ekleyerek kaynağının dışındaki uzak sunuculara erişim isteğinde bulunabilir.

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

Kaynaklar arası izin değerleri, aşağıdakiler gibi tam nitelikli ana makine adları olabilir:

  • "https://www.google.com/"
  • "https://www.gmail.com/"

Bunlar, aşağıdaki gibi eşleşme kalıpları da olabilir:

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

"https://*/" eşleşme kalıbı, erişilebilen tüm alanlara HTTPS erişimine izin verir. Buradaki eşleşme kalıplarının, içerik komut dosyası eşleşme kalıplarına benzer olduğunu ancak ana makineden sonraki yol bilgilerinin yoksayıldığını unutmayın.

Erişimin hem ana makineye hem de şemaya göre verildiğini de unutmayın. Bir uzantı, belirli bir ana makineye veya ana makine grubuna hem güvenli hem de güvenli olmayan HTTP erişimi istiyorsa izinleri ayrı ayrı bildirmesi gerekir:

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

Güvenlikle ilgili olarak göz önünde bulundurulması gerekenler

Siteler arası komut dosyası çalıştırma güvenlik açıklarından kaçınma

XMLHttpRequest aracılığıyla alınan kaynakları kullanırken arka plan sayfanızın siteler arası komut dosyası çalıştırma saldırısına karşı dikkatli olması gerekir. Özellikle aşağıdakiler gibi tehlikeli API'leri kullanmaktan kaçının:

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

Bunun yerine, komut dosyası çalıştırmayan daha güvenli API'leri tercih edin:

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

İçerik komut dosyası erişimini kaynaklar arası isteklere sınırlama

Bir içerik komut dosyası adına kaynaklar arası istekler gerçekleştirirken, bir içerik komut dosyasının kimliğine bürünmeye çalışabilecek kötü amaçlı web sayfalarına karşı dikkatli olun. Özellikle içerik komut dosyalarının rastgele bir URL istemesine izin vermeyin.

Bir uzantının, içerik komut dosyasının bir öğenin fiyatını öğrenmesine izin vermek için kaynaklar arası istekte bulunduğunu düşünelim. Bir (güvenli olmayan) yaklaşım, içerik komut dosyasının arka plan sayfası tarafından getirilecek tam kaynağı belirtmesini sağlamaktır.

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

Yukarıdaki yaklaşımda, içerik komut dosyası uzantıdan, uzantının erişebildiği herhangi bir URL'yi getirmesini isteyebilir. Kötü amaçlı bir web sayfası bu tür mesajları sahteleştirip uzantıyı kandırarak kaynaklar arası erişim izni vermeye ikna edebilir.

Bunun yerine, getirilebilecek kaynakları sınırlayan ileti işleyiciler tasarlayın. Aşağıda, tam URL değil yalnızca itemId içerik komut dosyası tarafından sağlanmaktadır.

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 => ...);

HTTP yerine HTTPS'yi tercih etme

Ayrıca, HTTP üzerinden alınan kaynaklara özellikle dikkat edin. Uzantınız saldırıya açık bir ağda kullanılıyorsa ağ saldırganı (diğer adıyla "man-in-the-middle") yanıtı değiştirebilir ve uzantınıza saldırabilir. Bunun yerine, mümkün olduğunda HTTPS'yi tercih edin.

İçerik güvenlik politikasını ayarlama

Manifest dosyanıza content_security_policy özelliği ekleyerek uygulamalar veya uzantılar için varsayılan İçerik Güvenliği Politikası'nı değiştirirseniz bağlanmak istediğiniz tüm ana bilgisayarlara izin verildiğinden emin olmanız gerekir. Varsayılan politika, ana makinelere bağlantıları kısıtlamasa da connect-src veya default-src yönergelerini açıkça eklerken dikkatli olun.