Kaynaklar arası XMLHttpRequest

Normal web sayfaları, uzak sunuculardan veri gönderip almak için XMLHttpRequest nesnesini kullanabilir ancak aynı kaynak politikasıyla sınırlıdır. İçerik komut dosyaları, içerik komut dosyasının yerleştirildiği web kaynağı adına istek başlatır. Bu nedenle içerik komut dosyaları da aynı kaynak politikasına tabidir. (İçerik komut dosyaları, Chrome 73'ten itibaren CORB'ye, Chrome 83'ten itibaren CORS'a tabidir.) Uzantı kaynakları çok sınırlı değildir. Bir uzantının arka plan sayfasında veya ön plan sekmesinde çalışan 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 ayrı güvenlik kaynağı içinde bulunur. Uzantı, ek ayrıcalık istemeden, kurulumu sırasındaki kaynakları almak için XMLHttpRequest özelliğini kullanabilir. Örneğin, bir uzantı config_resources klasöründe config.json adlı bir JSON yapılandırma dosyası içeriyorsa uzantı dosya 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ı kendi dışında bir güvenlik kaynağını (https://www.google.com) kullanmaya çalışırsa ve gerekli çapraz kaynak izinleri istemediği sürece tarayıcı buna izin vermez.

Kaynaklar arası izinler isteme

Uzantı, manifest dosyasının izinler bölümüne ana makine veya ana makine eşleşme kalıpları (ya da her ikisi) ekleyerek kaynak 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/"

Ya da aşağıdakiler gibi eşleşme kalıpları olabilir:

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

"https://*/" eşleşme kalıbı, erişilebilir tüm alanlara HTTPS erişimi sağlar. Burada eşleşme kalıplarının içerik komut dosyası eşleşme kalıplarına benzer olduğunu ancak ana makineyi izleyen tüm yol bilgilerinin yok sayıldığını unutmayın.

Ayrıca, erişim hem ana makineye hem de şemaya göre verildiğini 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ı olarak tanımlamalıdır:

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

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

Siteler arası komut dosyası güvenlik açıklarını önleme

XMLHttpRequest aracılığıyla alınan kaynakları kullanırken arka plan sayfanız, siteler arası komut dosyası çalıştırmanın kurbanına düşmemeye dikkat etmelidir. Ö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 dosyalarını ç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();

Kaynaklar arası isteklerle içerik komut dosyası erişimini sınırlama

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

Bir uzantının, içerik komut dosyasının bir öğenin fiyatını keşfetmesini sağlamak için kaynaklar arası istek gerçekleştirdiği bir örneği düşünün. Bunlardan biri (güvenli olmayan) 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ı oluşturup uzantıyı kaynaklar arası kaynaklara erişim sağlamak için kandırabilir.

Bunun yerine, alınabilecek kaynakları sınırlayan mesaj işleyicileri tasarlayın. Aşağıda, içerik komut dosyası tarafından yalnızca itemId sağlanır, tam URL sağlanmaz.

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 aracılığıyla alınan kaynaklar konusunda özellikle dikkatli olun. Uzantınız saldırgan bir ağda kullanılıyorsa bir ağ saldırganı ("man-in-the-middle" olarak da bilinir) yanıtı değiştirebilir ve potansiyel olarak uzantınıza saldırabilir. Bunun yerine, mümkün olduğunda HTTPS'yi tercih edin.

İçerik güvenliği politikasını düzenleme

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