XMLHttpRequest แบบข้ามต้นทาง

หน้าเว็บทั่วไปสามารถใช้ออบเจ็กต์ XMLHttpRequest เพื่อส่งและรับข้อมูลจากเซิร์ฟเวอร์ระยะไกล ได้ แต่จะถูกจำกัดโดย นโยบายต้นทางเดียวกัน สคริปต์เนื้อหาจะเริ่มคำขอ ในนามของต้นทางเว็บที่สคริปต์เนื้อหาถูกแทรกลงไป ดังนั้นสคริปต์ เนื้อหาจึงอยู่ภายใต้นโยบายต้นทางเดียวกันด้วย (สคริปต์เนื้อหาอยู่ภายใต้ CORB ตั้งแต่ Chrome 73 และ CORS ตั้งแต่ Chrome 83) ต้นทางของส่วนขยายไม่ได้ถูกจำกัดมากนัก โดยสคริปต์ที่ทำงานในหน้าพื้นหลังหรือแท็บเบื้องหน้าของส่วนขยายสามารถสื่อสารกับเซิร์ฟเวอร์ระยะไกลภายนอกต้นทางได้ ตราบใดที่ส่วนขยายขอสิทธิ์ข้ามต้นทาง

ต้นทางของส่วนขยาย

ส่วนขยายแต่ละรายการที่ทำงานอยู่จะมีต้นทางด้านความปลอดภัยแยกกัน โดยไม่ต้องขอสิทธิ์เพิ่มเติม ส่วนขยายสามารถใช้ XMLHttpRequest เพื่อรับทรัพยากรภายในส่วนขยายที่ติดตั้ง ตัวอย่างเช่น หากส่วนขยายมีไฟล์การกำหนดค่า JSON ชื่อ config.json ในโฟลเดอร์ config_resources ส่วนขยายจะดึงข้อมูลเนื้อหาของไฟล์ได้ดังนี้

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 เบราว์เซอร์จะไม่อนุญาต เว้นแต่ว่าส่วนขยายจะขอสิทธิ์ข้ามต้นทางที่เหมาะสม

การขอสิทธิ์ข้ามต้นทาง

การเพิ่มโฮสต์หรือรูปแบบการจับคู่โฮสต์ (หรือทั้ง 2 อย่าง) ลงในส่วนสิทธิ์ของ ไฟล์ Manifestจะช่วยให้ส่วนขยายขอสิทธิ์เข้าถึงเซิร์ฟเวอร์ระยะไกลภายนอกต้นทางของส่วนขยายได้

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

ค่าสิทธิ์ข้ามต้นทางอาจเป็นชื่อโฮสต์แบบสมบูรณ์ในตัวเอง เช่น

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

หรืออาจเป็นรูปแบบการจับคู่ เช่น

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

รูปแบบการจับคู่ "https://*/" อนุญาตให้เข้าถึง HTTPS สำหรับโดเมนทั้งหมดที่เข้าถึงได้ โปรดทราบว่ารูปแบบการจับคู่ในที่นี้จะคล้ายกับรูปแบบการจับคู่สคริปต์เนื้อหา แต่ระบบจะละเว้นข้อมูลเส้นทางที่อยู่หลังโ101}ฮสต์

โปรดทราบว่าระบบจะให้สิทธิ์เข้าถึงทั้งตามโฮสต์และตามรูปแบบ หากส่วนขยายต้องการเข้าถึง HTTP ทั้งแบบปลอดภัยและไม่ปลอดภัยสำหรับโฮสต์หรือชุดโฮสต์ที่กำหนด ส่วนขยายจะต้องประกาศสิทธิ์แยกกัน

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

ข้อควรพิจารณาด้านความปลอดภัย

การหลีกเลี่ยงช่องโหว่ของ Cross-site Scripting

เมื่อใช้ทรัพยากรที่ดึงข้อมูลผ่าน XMLHttpRequest หน้าพื้นหลังควรระมัดระวังไม่ให้ตกเป็นเหยื่อของ Cross-site Scripting โดยเฉพาะอย่างยิ่ง ให้หลีกเลี่ยงการใช้ API ที่เป็นอันตราย เช่น 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 ที่ปลอดภัยกว่าซึ่งไม่เรียกใช้สคริปต์แทน

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

การจำกัดการเข้าถึงคำขอข้ามต้นทางของสคริปต์เนื้อหา

เมื่อทำการขอข้ามต้นทางในนามของสคริปต์เนื้อหา โปรดระมัดระวังเพื่อ ป้องกัน หน้าเว็บที่เป็นอันตราย ซึ่งอาจพยายามแอบอ้างเป็นสคริปต์เนื้อหา โดยเฉพาะอย่างยิ่ง ห้ามอนุญาตให้สคริปต์เนื้อหาขอ URL ที่กำหนดเอง

ลองพิจารณาตัวอย่างที่ส่วนขยายทำการขอข้ามต้นทางเพื่อให้สคริปต์เนื้อหาค้นหาราคาของสินค้า วิธีหนึ่ง (ที่ไม่ปลอดภัย) คือการให้สคริปต์เนื้อหาระบุทรัพยากรที่แน่นอนที่หน้าพื้นหลังจะดึงข้อมูล

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

ในวิธีด้านบน สคริปต์เนื้อหาสามารถขอให้ส่วนขยายดึงข้อมูล URL ใดก็ได้ที่ส่วนขยายมีสิทธิ์เข้าถึง หน้าเว็บที่เป็นอันตรายอาจสามารถปลอมแปลงข้อความดังกล่าวและหลอกให้ส่วนขยายให้สิทธิ์เข้าถึงทรัพยากรข้ามต้นทาง

แต่ให้ออกแบบตัวแฮนเดิลข้อความที่จำกัดทรัพยากรที่ดึงข้อมูลได้ โดยด้านล่างนี้ สคริปต์เนื้อหาจะให้เฉพาะ itemId ไม่ใช่ URL แบบเต็ม

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ลงใน Manifest คุณจะต้องตรวจสอบว่าระบบอนุญาตโฮสต์ที่คุณต้องการเชื่อมต่อ แม้ว่านโยบายเริ่มต้นจะไม่ได้จำกัดการเชื่อมต่อกับโฮสต์ แต่โปรดระมัดระวังเมื่อเพิ่มคำสั่ง connect-src หรือ default-src อย่างชัดแจ้ง