XMLHttpRequest lintas asal

Halaman web biasa dapat menggunakan objek XMLHttpRequest untuk mengirim dan menerima data dari jarak jauh server, tetapi dibatasi oleh kebijakan asal yang sama. Skrip konten memulai permintaan atas nama asal web tempat skrip konten dimasukkan dan oleh karena itu konten skrip juga tunduk pada kebijakan asal yang sama. (Skrip konten telah tunduk kepada CORB sejak Chrome 73 dan CORS sejak Chrome 83.) Asal ekstensi tidak begitu terbatas - skrip yang berjalan di laman latar belakang atau tab latar depan ekstensi dapat berkomunikasi dengan server jarak jauh di luar ke asalnya, selama ekstensi meminta izin lintas origin.

Asal ekstensi

Setiap ekstensi yang berjalan berada dalam asal keamanannya sendiri yang terpisah. Tanpa meminta informasi tambahan ekstensi akses dapat menggunakan XMLHttpRequest untuk mendapatkan sumber daya di dalam penginstalannya. Sebagai jika ekstensi berisi file konfigurasi JSON yang disebut config.json, di config_resources, ekstensi dapat mengambil konten file seperti ini:

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

Jika ekstensi mencoba menggunakan asal keamanan selain itu sendiri, ucapkan https://www.google.com, browser tidak akan mengizinkannya kecuali jika ekstensi telah meminta permintaan izin izin akses.

Meminta izin lintas origin

Dengan menambahkan pola pencocokan host atau host (atau keduanya) ke bagian izin dalam manifes, ekstensi dapat meminta akses ke server jarak jauh di luar asalnya.

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

Nilai izin lintas asal dapat berupa nama host yang sepenuhnya memenuhi syarat, seperti berikut:

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

Atau bisa juga berupa pola pencocokan, seperti ini:

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

Pola kecocokan "https://*/" memungkinkan akses HTTPS ke semua domain yang dapat dijangkau. Perhatikan bahwa di sini, cocokkan serupa dengan pola pencocokan skrip konten, tetapi informasi jalur apa pun yang mengikuti host diabaikan.

Perhatikan juga bahwa akses diberikan oleh host dan skema. Jika sebuah ekstensi menginginkan hal yang aman dan akses HTTP yang tidak aman ke host atau sekumpulan host tertentu, maka harus mendeklarasikan izin secara terpisah:

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

Pertimbangan keamanan

Menghindari kerentanan pembuatan skrip lintas situs

Saat menggunakan resource yang diambil melalui XMLHttpRequest, halaman latar belakang harus berhati-hati agar tidak menjadi korban pembuatan skrip lintas situs. Secara khusus, hindari penggunaan API berbahaya seperti di bawah ini:

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

Sebagai gantinya, pilih API yang lebih aman yang tidak menjalankan skrip:

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

Membatasi akses skrip konten ke permintaan lintas origin

Saat menjalankan permintaan lintas origin atas nama skrip konten, berhati-hatilah untuk melindungi halaman web berbahaya yang mungkin mencoba meniru identitas skrip konten. Secara khusus, jangan izinkan skrip konten untuk meminta URL arbitrer.

Pertimbangkan contoh saat ekstensi melakukan permintaan lintas origin untuk mengizinkan skrip konten menemukan harga suatu item. Satu pendekatan (tidak aman) adalah meminta skrip konten menentukan resource persis yang akan diambil oleh halaman latar belakang.

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

Dalam pendekatan di atas, skrip konten dapat meminta ekstensi untuk mengambil URL apa pun yang dapat diakses. Halaman web berbahaya mungkin dapat memalsukan pesan tersebut dan mengelabui ekstensi untuk yang memberikan akses ke resource lintas origin.

Sebagai gantinya, desainlah pengendali pesan yang membatasi resource yang bisa diambil. Di bawah ini, hanya itemId diberikan oleh skrip konten, dan bukan URL lengkap.

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

Lebih memilih HTTPS daripada HTTP

Selain itu, Anda harus sangat berhati-hati dengan resource yang diambil melalui HTTP. Jika ekstensi Anda digunakan di jaringan yang berbahaya, penyerang jaringan (alias "man-in-the-middle") dapat memodifikasi respons dan berpotensi menyerang ekstensi Anda. Sebagai gantinya, pilih HTTPS jika memungkinkan.

Menyesuaikan kebijakan keamanan konten

Jika Anda mengubah Kebijakan Keamanan Konten default untuk aplikasi atau ekstensi dengan menambahkan elemen content_security_policy ke manifes, Anda harus memastikan bahwa setiap host yang yang ingin Anda hubungkan diizinkan. Meskipun kebijakan {i>default<i} tidak membatasi koneksi ke {i>host<i}, berhati-hatilah saat menambahkan perintah connect-src atau default-src secara eksplisit.