โดยหน้าเว็บปกติจะใช้ API ของ fetch()
หรือ XMLHttpRequest
เพื่อส่งและรับข้อมูลจากเซิร์ฟเวอร์ระยะไกลได้ แต่ถูกจำกัดโดยนโยบายต้นทางเดียวกัน สคริปต์เนื้อหาจะส่งคำขอในนามของต้นทางเว็บที่มีการแทรกสคริปต์เนื้อหาลงไป ดังนั้นสคริปต์เนื้อหาจึงอยู่ภายใต้นโยบายต้นทางเดียวกันเช่นกัน ที่มาของส่วนขยายยังมีไม่จํากัดมากนัก สคริปต์ที่ดำเนินการในแท็บบริการส่วนขยายหรือแท็บเบื้องหน้าสามารถสื่อสารกับเซิร์ฟเวอร์ระยะไกลนอกต้นทางได้ ตราบใดที่ส่วนขยายขอสิทธิ์แบบข้ามต้นทาง
ที่มาของส่วนขยาย
ส่วนขยายที่ทำงานอยู่แต่ละรายการจะมีอยู่ในต้นทางการรักษาความปลอดภัยที่แยกกัน ส่วนขยายจะเรียกใช้ fetch()
เพื่อรับทรัพยากรภายในการติดตั้งได้โดยไม่ต้องขอสิทธิ์เพิ่มเติม ตัวอย่างเช่น หากส่วนขยายมีไฟล์การกำหนดค่า JSON ที่ชื่อว่า config.json
ในโฟลเดอร์ config_resources/
ส่วนขยายสามารถเรียกข้อมูลเนื้อหาของไฟล์ได้ด้วยวิธีต่อไปนี้
const response = await fetch('/config_resources/config.json');
const jsonData = await response.json();
หากส่วนขยายพยายามใช้ต้นทางการรักษาความปลอดภัยอื่นๆ เช่น https://www.google.com เบราว์เซอร์จะไม่อนุญาต เว้นแต่ส่วนขยายจะส่งคำขอสิทธิ์แบบข้ามต้นทางที่เหมาะสม
ขอสิทธิ์แบบข้ามต้นทาง
หากต้องการขอสิทธิ์เข้าถึงเซิร์ฟเวอร์ระยะไกลนอกต้นทางของส่วนขยาย ให้เพิ่มโฮสต์ รูปแบบการจับคู่ หรือทั้ง 2 ส่วนในส่วน host_permissions ของไฟล์ไฟล์ Manifest
{
"name": "My extension",
...
"host_permissions": [
"https://www.google.com/"
],
...
}
ค่าสิทธิ์แบบข้ามต้นทางอาจเป็นชื่อโฮสต์ที่ตรงตามเกณฑ์ทั้งหมด เช่น
- "https://www.google.com/"
- "https://www.gmail.com/"
หรืออาจเป็นรูปแบบการจับคู่ เช่น
- "https://*.google.com/"
- "https://*/"
รูปแบบการจับคู่ "https://*/" จะอนุญาตให้ HTTPS เข้าถึงโดเมนที่เข้าถึงได้ทั้งหมด โปรดทราบว่ารูปแบบการจับคู่จะคล้ายกับรูปแบบการจับคู่สคริปต์เนื้อหาในส่วนนี้ แต่ระบบจะไม่สนใจข้อมูลเส้นทางที่ติดตามโฮสต์
โปรดทราบว่าระบบจะให้สิทธิ์เข้าถึงทั้งแก่โฮสต์และตามรูปแบบ หากส่วนขยายต้องการสิทธิ์เข้าถึง HTTP ทั้งที่ปลอดภัยและไม่ปลอดภัยสำหรับโฮสต์หรือชุดโฮสต์หนึ่งๆ ส่วนขยายนั้นจะต้องประกาศสิทธิ์แยกต่างหาก ดังนี้
"host_permissions": [
"http://www.google.com/",
"https://www.google.com/"
]
Fetch() กับ XMLHttpRequest()
fetch()
สร้างขึ้นสำหรับ Service Worker โดยเฉพาะและทำตามเทรนด์ของเว็บที่กว้างขึ้นจากการทำงานแบบพร้อมกัน ส่วนขยายนอก Service Worker รองรับ XMLHttpRequest()
API และการเรียกใช้ API นี้จะทริกเกอร์เครื่องจัดการการดึงข้อมูลของโปรแกรมทำงานของบริการส่วนขยาย ผลงานใหม่ควรมีความสำคัญมากกว่า fetch()
หากเป็นไปได้
ข้อควรพิจารณาด้านความปลอดภัย
หลีกเลี่ยงช่องโหว่ในการเขียนสคริปต์ข้ามเว็บไซต์
เมื่อใช้ทรัพยากรที่ดึงข้อมูลผ่าน fetch()
เอกสารนอกจอ แผงด้านข้าง หรือป๊อปอัปควรระวังอย่าปล่อยให้เกิดCross-site Scripting หลีกเลี่ยงการใช้ API ที่เป็นอันตราย เช่น innerHTML
เช่น
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;
...
แต่แนะนำให้ใช้ API ที่ปลอดภัยกว่าซึ่งไม่เรียกใช้สคริปต์แทน ดังนี้
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;
จำกัดการเข้าถึงสคริปต์เนื้อหาให้กับคำขอข้ามต้นทาง
เมื่อดำเนินการคำขอข้ามต้นทางในนามของสคริปต์เนื้อหา ให้ระมัดระวังป้องกันหน้าเว็บที่เป็นอันตรายที่อาจพยายามแอบอ้างเป็นสคริปต์เนื้อหา โดยเฉพาะอย่างยิ่ง ไม่อนุญาตให้สคริปต์เนื้อหาขอ 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') {
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 => ...
);
ใช้ HTTPS แทน HTTP
นอกจากนี้ โปรดระวังการใช้ทรัพยากรที่ดึงข้อมูลผ่าน HTTP เป็นพิเศษ หากใช้ส่วนขยายของคุณในเครือข่ายที่ไม่เป็นมิตร ผู้โจมตีเครือข่าย (หรือที่เรียกว่า "man-in-the-middle") จะแก้ไขการตอบสนองและอาจโจมตีส่วนขยายของคุณได้ หากเป็นไปได้ ให้ใช้ HTTPS แทน
ปรับนโยบายความปลอดภัยของเนื้อหา
หากคุณแก้ไขนโยบายรักษาความปลอดภัยเนื้อหาเริ่มต้นสำหรับส่วนขยายโดยการเพิ่มแอตทริบิวต์ content_security_policy
ลงในไฟล์ Manifest คุณจะต้องตรวจสอบว่าโฮสต์ทั้งหมดที่คุณต้องการเชื่อมต่อได้รับอนุญาต แม้ว่านโยบายเริ่มต้นจะไม่จํากัดการเชื่อมต่อกับโฮสต์ แต่โปรดระมัดระวังเมื่อเพิ่มคําสั่ง connect-src
หรือ default-src
อย่างชัดแจ้ง