นโยบายรักษาความปลอดภัยเนื้อหา

โมเดลความปลอดภัยของเว็บมีการรูทในนโยบายต้นทางเดียวกัน โค้ดจาก https://mybank.com ควรมีสิทธิ์เข้าถึงข้อมูลของ https://mybank.com เท่านั้น และ https://evil.example.com ไม่ควรจะได้รับอนุญาตให้เข้าถึงแน่นอน ต้นทางแต่ละแห่งจะแยกออกจากส่วนอื่นๆ ในเว็บ ทำให้นักพัฒนาซอฟต์แวร์มีแซนด์บ็อกซ์ที่ปลอดภัยสำหรับสร้างและเล่น ในทางทฤษฎี สิ่งนี้สุดยอดจริงๆ ในทางปฏิบัติ ผู้โจมตีพบวิธีการที่ชาญฉลาดในการโค่นล้มระบบ

ตัวอย่างเช่น การโจมตีแบบ cross-site Scripting (XSS) ข้ามนโยบายต้นทางเดียวกันโดยหลอกลวงให้เว็บไซต์ส่งโค้ดที่เป็นอันตรายไปพร้อมกับเนื้อหาที่ตั้งใจ นี่เป็นปัญหาใหญ่เนื่องจากเบราว์เซอร์เชื่อถือโค้ดทั้งหมดที่แสดงบนหน้าเว็บว่าเป็นส่วนหนึ่งของต้นทางด้านความปลอดภัยของหน้าเว็บโดยชอบธรรม ข้อมูลโกง XSS เป็นข้อมูลเก่าและเป็นตัวแทนของวิธีการที่ผู้โจมตีอาจใช้เพื่อละเมิดความไว้วางใจนี้โดยการแทรกโค้ดที่เป็นอันตราย ถ้าผู้โจมตีแทรกโค้ดใดๆ ลงไปได้ ก็ถือว่าจบเกมมากทีเดียว ข้อมูลเซสชันของผู้ใช้จะถูกบุกรุก และข้อมูลที่ควรเก็บไว้เป็นความลับจะถูกขโมยไปโดย BadGuys ซึ่งแน่นอนว่าเราต้องป้องกันหากทำได้

ภาพรวมนี้ไฮไลต์การป้องกันที่สามารถลดความเสี่ยงและผลกระทบจากการโจมตี XSS ในเบราว์เซอร์สมัยใหม่ได้อย่างมาก: นโยบายรักษาความปลอดภัยเนื้อหา (CSP)

สรุปคร่าวๆ

  • ใช้รายการที่อนุญาตเพื่อบอกลูกค้าว่าสินค้าใดได้รับอนุญาตให้ขาย และสินค้าใดที่ไม่ได้รับอนุญาต
  • ดูคำสั่งที่ใช้ได้
  • เรียนรู้เกี่ยวกับคีย์เวิร์ดที่พวกเขาใช้
  • โค้ดในบรรทัดและ eval() ถือว่าเป็นอันตราย
  • รายงานการละเมิดนโยบายให้เซิร์ฟเวอร์ทราบก่อนที่จะบังคับใช้

รายการที่อนุญาตสำหรับแหล่งที่มา

ปัญหาที่ถูกโจมตีจากการโจมตี XSS คือการที่เบราว์เซอร์ไม่สามารถแยกแยะระหว่างสคริปต์ที่เป็นส่วนหนึ่งของแอปพลิเคชันของคุณและสคริปต์ที่แทรกเข้ามาอย่างมุ่งร้ายโดยบุคคลที่สาม ตัวอย่างเช่น ปุ่ม Google +1 ที่ด้านล่างของหน้าจะโหลดและเรียกใช้โค้ดจาก https://apis.google.com/js/plusone.js ในบริบทของต้นทางของหน้านี้ เราเชื่อถือโค้ดนั้นได้ แต่ไม่อาจคาดหวังได้ว่าเบราว์เซอร์จะรู้ด้วยตัวเองว่าโค้ดจาก apis.google.com เป็นโค้ดที่ยอดเยี่ยม ส่วนโค้ดจาก apis.evil.example.com อาจจะไม่ใช่โค้ดดังกล่าว เบราว์เซอร์จะดาวน์โหลดและเรียกใช้โค้ดที่หน้าเว็บร้องขอ โดยไม่คำนึงถึงแหล่งที่มา

CSP จะกำหนดส่วนหัว HTTP ของ Content-Security-Policy ซึ่งจะให้คุณสร้างรายการแหล่งที่มาของเนื้อหาที่เชื่อถือได้ที่อนุญาต และสั่งให้เบราว์เซอร์เรียกใช้หรือแสดงผลเฉพาะทรัพยากรจากแหล่งที่มาเหล่านั้น แทนการค่อยๆ เชื่อทุกอย่างที่เซิร์ฟเวอร์ส่งมา แม้ว่าผู้โจมตีจะหาช่องโหว่ให้แทรกสคริปต์ได้ แต่สคริปต์จะไม่ตรงกับรายการที่อนุญาตและจะไม่ถูกเรียกใช้

เนื่องจากเราเชื่อมั่นว่า apis.google.com จะส่งโค้ดที่ถูกต้อง และเราเชื่อมั่นว่าจะทำเช่นเดียวกัน เราจึงกำหนดนโยบายที่อนุญาตให้สคริปต์ทำงานเฉพาะเมื่อสคริปต์มาจากแหล่งที่มา 1 ใน 2 แหล่งที่มาต่อไปนี้

Content-Security-Policy: script-src 'self' https://apis.google.com

ง่ายใช่ไหม คุณคงพอจะเดาได้ว่า script-src เป็นคำสั่งที่ควบคุมชุดสิทธิ์ที่เกี่ยวข้องกับสคริปต์ของหน้าหนึ่งๆ เราได้ระบุ 'self' เป็นแหล่งที่มาที่ถูกต้องของสคริปต์แหล่งหนึ่ง และระบุ https://apis.google.com เป็นอีกแหล่งที่มาหนึ่ง เบราว์เซอร์จะดาวน์โหลดและเรียกใช้ JavaScript อย่างถูกต้องจาก apis.google.com ผ่าน HTTPS รวมถึงจากต้นทางของหน้าปัจจุบัน

ข้อผิดพลาดของคอนโซล: ปฏิเสธการโหลดสคริปต์ "http://evil.example.com/evil.js" เพราะละเมิดคำสั่งของนโยบายรักษาความปลอดภัยเนื้อหา: Script-src 'self' https://apis.google.com

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

นโยบายมีผลกับแหล่งข้อมูลที่หลากหลาย

แม้ว่าทรัพยากรสคริปต์จะเป็นความเสี่ยงด้านความปลอดภัยที่เห็นได้ชัดเจนที่สุด แต่ CSP มีชุดคำสั่งนโยบายที่สมบูรณ์ซึ่งช่วยให้ควบคุมทรัพยากรที่หน้าเว็บอนุญาตให้โหลดได้อย่างละเอียด คุณได้เห็น script-src แล้ว แนวคิดจึงควรชัดเจน

มาดูคำสั่งทรัพยากรที่เหลือกันอย่างคร่าวๆ รายการด้านล่างแสดงสถานะของคำสั่งในระดับ 2 มีการเผยแพร่ข้อกำหนดระดับ 3 แล้ว แต่ไม่ได้นำไปใช้งานส่วนใหญ่ในเบราว์เซอร์หลัก

  • base-uri จำกัด URL ที่ปรากฏในองค์ประกอบ <base> ของหน้า
  • child-src แสดงรายการ URL ของผู้ปฏิบัติงานและเนื้อหาเฟรมแบบฝัง ตัวอย่างเช่น child-src https://youtube.com จะเปิดใช้การฝังวิดีโอจาก YouTube แต่ไม่ได้เปิดใช้จากต้นทางอื่นๆ
  • connect-src จำกัดต้นทางที่คุณเชื่อมต่อได้ (ผ่าน XHR, WebSockets และ EventSource)
  • font-src ระบุต้นทางที่แสดงแบบอักษรของเว็บได้ เปิดใช้แบบอักษรเว็บของ Google ได้ผ่าน font-src https://themes.googleusercontent.com
  • form-action แสดงรายการปลายทางที่ถูกต้องสำหรับการส่งจากแท็ก <form>
  • frame-ancestors ระบุแหล่งที่มาที่สามารถฝังหน้าปัจจุบันได้ คำสั่งนี้ใช้กับแท็ก <frame>, <iframe>, <embed> และ <applet> คำสั่งนี้ใช้ได้ในแท็ก <meta> ไม่ได้ และจะใช้กับทรัพยากรที่ไม่ใช่ HTML เท่านั้น
  • frame-src เลิกใช้งานแล้วในระดับ 2 แต่ได้รับการกู้คืนในระดับ 3 หากไม่ได้นำเสนอ ระบบจะยังกลับไปใช้ child-src เหมือนเดิม
  • img-src กำหนดจุดเริ่มต้นสำหรับการโหลดรูปภาพ
  • media-src จำกัดต้นทางที่อนุญาตให้ส่งวิดีโอและเสียง
  • object-src ช่วยให้ควบคุม Flash และปลั๊กอินอื่นๆ ได้
  • plugin-types จำกัดประเภทของปลั๊กอินที่หน้าเว็บอาจเรียกใช้
  • report-uri ระบุ URL ที่เบราว์เซอร์จะส่งรายงานเมื่อมีการละเมิดนโยบายรักษาความปลอดภัยเนื้อหา ใช้คำสั่งนี้ในแท็ก <meta> ไม่ได้
  • style-src เป็นคู่ของ script-src สำหรับสไตล์ชีต
  • upgrade-insecure-requests จะสั่งให้ User Agent เขียนรูปแบบ URL ใหม่ ซึ่งจะเปลี่ยน HTTP เป็น HTTPS คำสั่งนี้มีไว้สำหรับเว็บไซต์ที่มี URL เก่าจำนวนมากซึ่งต้องเขียนใหม่
  • worker-src เป็นคำสั่ง CSP ระดับ 3 ที่จำกัด URL ที่อาจโหลดในฐานะผู้ปฏิบัติงาน ผู้ปฏิบัติงานที่แชร์ หรือ Service Worker คำสั่งนี้มีการติดตั้งใช้งานแบบจำกัดตั้งแต่เดือนกรกฎาคม 2017 เป็นต้นไป

คำสั่งจะเปิดอยู่โดยค่าเริ่มต้น หากคุณไม่ได้กำหนดนโยบายที่เฉพาะเจาะจงสำหรับคำสั่ง เช่น font-src คำสั่งนั้นจะทำงานโดยค่าเริ่มต้นแม้ว่าคุณจะระบุ * เป็นแหล่งที่มาที่ถูกต้อง (เช่น คุณจะโหลดแบบอักษรได้จากทุกที่โดยไม่มีข้อจำกัด)

คุณลบล้างลักษณะการทำงานเริ่มต้นนี้ได้โดยระบุคำสั่ง default-src คำสั่งนี้จะกำหนดค่าเริ่มต้นสำหรับคำสั่งส่วนใหญ่ที่คุณไม่ระบุไว้ โดยทั่วไป การตั้งค่านี้จะมีผลกับคำสั่งที่ลงท้ายด้วย -src หากตั้งค่า default-src เป็น https://example.com และคุณระบุคำสั่ง font-src ไม่สำเร็จ คุณจะโหลดแบบอักษรจาก https://example.com ได้โดยไม่ต้องโหลดที่อื่น เราระบุเพียง script-src ในตัวอย่างก่อนหน้านี้ ซึ่งหมายความว่าคุณจะโหลดรูปภาพ แบบอักษร และอื่นๆ จากต้นทางใดก็ได้

คำสั่งต่อไปนี้ไม่ได้ใช้ default-src เป็นคำสั่งสำรอง จำไว้ว่า การไม่ตั้งค่าสิ่งต่างๆ ก็เหมือนกับการ อนุญาตทุกอย่าง

  • base-uri
  • form-action
  • frame-ancestors
  • plugin-types
  • report-uri
  • sandbox

คุณจะใช้คำสั่งเหล่านี้กี่รายการก็ได้เท่าที่เหมาะสมสำหรับแอปพลิเคชันนั้นๆ เพียงระบุคำสั่งแต่ละรายการในส่วนหัวของ HTTP โดยคั่นด้วยเครื่องหมายอัฒภาค ตรวจสอบว่าได้ระบุทรัพยากรที่จำเป็นทั้งหมดของประเภทที่เฉพาะเจาะจงไว้ในคำสั่ง single แล้ว ถ้าคุณเขียนบางอย่าง เช่น script-src https://host1.com; script-src https://host2.com ระบบจะไม่สนใจคำสั่งที่ 2 ตัวอย่างวิธีต่อไปนี้ จะระบุให้ต้นทางทั้ง 2 แห่งถูกต้อง

script-src https://host1.com https://host2.com

ตัวอย่างเช่น หากคุณมีแอปพลิเคชันที่โหลดทรัพยากรทั้งหมดจากเครือข่ายนำส่งข้อมูล (เช่น https://cdn.example.net) และทราบว่าคุณไม่จำเป็นต้องใช้เนื้อหาหรือปลั๊กอินที่เฟรม นโยบายของคุณอาจมีลักษณะดังต่อไปนี้

Content-Security-Policy: default-src https://cdn.example.net; child-src 'none'; object-src 'none'

รายละเอียดการใช้งาน

คุณจะเห็นส่วนหัว X-WebKit-CSP และ X-Content-Security-Policy ในบทแนะนำต่างๆ บนเว็บ ตั้งแต่นี้เป็นต้นไป คุณควรเพิกเฉยต่อส่วนหัวที่มีคำนำหน้าเหล่านี้ เบราว์เซอร์ที่ทันสมัย (ยกเว้น IE) รองรับส่วนหัว Content-Security-Policy ที่ไม่มีคำนำหน้า ซึ่งเป็นส่วนหัวที่คุณควรใช้

ไม่ว่าคุณจะใช้ส่วนหัวแบบใดก็ตาม ระบบจะกำหนดนโยบายแบบหน้าต่อหน้า คุณจะต้องส่งส่วนหัวของ HTTP ไปพร้อมกับทุกคำตอบที่ต้องการเพื่อให้แน่ใจว่าจะได้รับการป้องกัน วิธีนี้มีความยืดหยุ่นมาก เพราะคุณสามารถปรับแต่งนโยบายสำหรับหน้าเว็บที่เฉพาะเจาะจงตามความต้องการที่เฉพาะเจาะจงได้ บางหน้าเว็บอาจมีหน้าหนึ่งในเว็บไซต์ของคุณมีปุ่ม +1 ในขณะที่บางชุดไม่มี คุณอาจอนุญาตให้โค้ดปุ่มโหลดเมื่อจำเป็นเท่านั้น

รายการแหล่งที่มาในแต่ละคำสั่งมีความยืดหยุ่น คุณระบุแหล่งที่มาตามรูปแบบ (data:, https:) หรือช่วงความจำเพาะจากชื่อโฮสต์เท่านั้นได้ (example.com ซึ่งจะตรงกับต้นทางในโฮสต์นั้น เช่น ชุดรูปแบบ พอร์ตใดก็ได้) ไปจนถึง URI แบบเต็ม (https://example.com:443 ซึ่งจับคู่กับ HTTPS เท่านั้น, เฉพาะ example.com และพอร์ต 443 เท่านั้น) ระบบยอมรับไวลด์การ์ด แต่ต้องอยู่ในรูปแบบรูปแบบ พอร์ต หรือในตำแหน่งด้านซ้ายสุดของชื่อโฮสต์เท่านั้น *://*.example.com:* จะจับคู่โดเมนย่อยทั้งหมดของ example.com (แต่ไม่ใช่ example.com เอง) โดยใช้รูปแบบใดก็ได้บนพอร์ตใดก็ได้

รายการแหล่งที่มายังยอมรับคีย์เวิร์ด 4 คำต่อไปนี้ด้วย

  • ส่วน 'none' จะไม่มีข้อมูลที่ตรงกัน อย่างที่คุณอาจคาดไว้
  • 'self' จะจับคู่ต้นทางปัจจุบัน แต่ไม่จับคู่กับโดเมนย่อย
  • 'unsafe-inline' อนุญาตให้ใช้ JavaScript และ CSS ในหน้าได้ (เราจะพูดถึงเรื่องนี้อย่างละเอียด อีกเล็กน้อย)
  • 'unsafe-eval' อนุญาตกลไกการแปลงข้อความเป็น JavaScript เช่น eval (เราจะพูดถึงเรื่องนี้เช่นกัน)

คีย์เวิร์ดเหล่านี้ต้องใช้เครื่องหมายคำพูดเดี่ยว ตัวอย่างเช่น script-src 'self' (มีเครื่องหมายคำพูด) ให้สิทธิ์การเรียกใช้ JavaScript จากโฮสต์ปัจจุบัน script-src self (ไม่มีเครื่องหมายคำพูด) อนุญาตให้ใช้ JavaScript จากเซิร์ฟเวอร์ชื่อ "self" (และ ไม่ใช่ จากโฮสต์ปัจจุบัน) ซึ่งอาจจะไม่ใช่อย่างที่คุณต้องการ

แซนด์บ็อกซ์

มีคำสั่งอีก 1 อย่างที่ควรพูดถึงคือ sandbox หน้าจะแตกต่างจากโปรแกรมอื่นๆ ที่เราดูแล้วเล็กน้อย เนื่องจากจะวางข้อจำกัดเกี่ยวกับการดำเนินการต่างๆ ที่หน้าเว็บนั้นสามารถทำได้ แทนที่จะเป็นทรัพยากรที่หน้าเว็บนั้นสามารถโหลดได้ หากมีคำสั่ง sandbox อยู่ ระบบจะถือว่าหน้าเว็บโหลดภายใน <iframe> ที่มีแอตทริบิวต์ sandbox ซึ่งอาจส่งผลกระทบที่หลากหลายต่อหน้าเว็บ ได้แก่ การบังคับให้หน้ามีต้นทางที่ไม่ซ้ำ การป้องกันไม่ให้ส่งแบบฟอร์ม และอื่นๆ แม้ว่าบทความนี้จะอยู่นอกเหนือขอบเขตของบทความนี้เล็กน้อย แต่คุณสามารถดูรายละเอียดทั้งหมดเกี่ยวกับแอตทริบิวต์แซนด์บ็อกซ์ที่ถูกต้องได้ในส่วน "แซนด์บ็อกซ์" ของข้อกำหนด HTML5

เมตาแท็ก

กลไกการนำส่งที่ต้องการของ CSP คือส่วนหัว HTTP อย่างไรก็ตาม การตั้งค่านโยบายในหน้าเว็บในมาร์กอัปโดยตรงอาจเป็นประโยชน์ ให้ใช้แท็ก <meta> ที่มีแอตทริบิวต์ http-equiv ดังนี้

<meta
  http-equiv="Content-Security-Policy"
  content="default-src https://cdn.example.net; child-src 'none'; object-src 'none'"
/>

ใช้ไม่ได้กับ frame-ancestors, report-uri หรือ sandbox

โค้ดในบรรทัดถือว่าเป็นอันตราย

ควรชัดเจนว่า CSP อิงตามต้นทางในรายการที่อนุญาต เนื่องจากเป็นวิธีการที่ชัดเจนในการสั่งให้เบราว์เซอร์จัดการชุดทรัพยากรบางอย่างให้อยู่ในระดับที่ยอมรับได้และต้องปฏิเสธทรัพยากรที่เหลือ รายการที่อนุญาตตามต้นทางจะไม่แก้ไขภัยคุกคามที่ใหญ่ที่สุดที่เกิดจากการโจมตี XSS ซึ่งก็คือการแทรกสคริปต์ในหน้า หากผู้โจมตีสามารถแทรกแท็กสคริปต์ที่มีเพย์โหลดที่เป็นอันตราย (<script>sendMyDataToEvilDotCom();</script>) โดยตรง เบราว์เซอร์จะไม่มีกลไกในการแยกเบราว์เซอร์จากแท็กสคริปต์ในหน้าที่ถูกต้อง CSP แก้ไขปัญหานี้โดยแบนสคริปต์แบบในหน้าทั้งหมด นี่เป็นวิธีเดียวที่แน่ใจได้

การแบนนี้ไม่เพียงแค่สคริปต์ที่ฝังในแท็ก script โดยตรง แต่ยังรวมถึงเครื่องจัดการเหตุการณ์ในบรรทัดและ URL javascript: คุณจะต้องย้ายเนื้อหาของแท็ก script ไปยังไฟล์ภายนอก และแทนที่ javascript: URL และ <a ... onclick="[JAVASCRIPT]"> ด้วยการเรียก addEventListener() ที่เหมาะสม ตัวอย่างเช่น คุณอาจเขียนคำสั่งต่อไปนี้ใหม่

<script>
  function doAmazingThings() {
    alert('YOU AM AMAZING!');
  }
</script>
<button onclick="doAmazingThings();">Am I amazing?</button>

ไปเป็นแบบอื่นๆ เช่น

<!-- amazing.html -->
<script src="amazing.js"></script>
<button id="amazing">Am I amazing?</button>

<div style="clear:both;"></div>
// amazing.js
function doAmazingThings() {
  alert('YOU AM AMAZING!');
}
document.addEventListener('DOMContentLoaded', function () {
  document.getElementById('amazing').addEventListener('click', doAmazingThings);
});

โค้ดที่เขียนใหม่มีข้อดีเหนือและมากกว่าการทำงานกับ CSP ได้ดี เนื่องจากเป็นแนวทางปฏิบัติที่ดีที่สุดอยู่แล้ว ไม่ว่าคุณจะใช้ CSP แบบใดก็ตาม โครงสร้างและลักษณะการทำงานของการผสมผสาน JavaScript ในบรรทัดในแบบที่คุณไม่ควรทำ เบราว์เซอร์สามารถแคชทรัพยากรภายนอกได้ง่ายขึ้น ทำให้นักพัฒนาซอฟต์แวร์เข้าใจได้มากขึ้น อีกทั้งช่วยให้มีการคอมไพล์และการลดขนาดลงด้วย คุณจะเขียนโค้ดได้ดีขึ้นหากคุณดำเนินการย้ายโค้ดไปยังทรัพยากรภายนอก

สไตล์อินไลน์จะมีการจัดการในลักษณะเดียวกัน นั่นคือควรรวมแอตทริบิวต์ style และแท็ก style ไว้ในสไตล์ชีตภายนอกเพื่อป้องกันวิธีการขโมยข้อมูลที่ชาญฉลาดอย่างไม่น่าเชื่อหลายแบบที่ CSS เปิดใช้

หากต้องมีสคริปต์และรูปแบบในหน้า ก็เปิดใช้ได้โดยการเพิ่ม 'unsafe-inline' เป็นแหล่งที่มาที่อนุญาตในคำสั่ง script-src หรือ style-src คุณสามารถใช้ Nonce หรือแฮชก็ได้ (ดูด้านล่าง) แต่จริงๆ แล้วไม่ควรทำ การแบนสคริปต์ในบรรทัดเป็นการรักษาความปลอดภัยของ CSP ที่ใหญ่ที่สุด และแบนรูปแบบอินไลน์จะทำให้แอปพลิเคชันของคุณแข็งแกร่งขึ้นเช่นกัน เราจะต้องใช้ความพยายามเล็กน้อยเพื่อให้แน่ใจว่าสิ่งต่างๆ ทำงานได้อย่างถูกต้องหลังจากที่ย้ายโค้ดทั้งหมดออกไปจากที่อื่น แต่ก็เป็นสิ่งตอบแทนที่คุ้มค่า

หากคุณจำเป็นต้องใช้

CSP ระดับ 2 มีความเข้ากันได้แบบย้อนหลังสำหรับสคริปต์แบบในหน้า โดยให้คุณเพิ่มสคริปต์แบบในหน้าที่เจาะจงลงในรายการที่อนุญาตได้โดยใช้ค่า Nonce ของการเข้ารหัส (จำนวนที่ใช้ครั้งเดียว) หรือแฮช แม้วิธีนี้อาจยุ่งยาก แต่ก็ช่วยได้

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

<script nonce="EDNnf03nceIOfn39fn3e9h3sdfa">
  // Some inline code I can't remove yet, but need to asap.
</script>

ตอนนี้ ให้เพิ่ม Nonce ลงในคำสั่ง script-src ต่อท้ายคีย์เวิร์ด nonce-

Content-Security-Policy: script-src 'nonce-EDNnf03nceIOfn39fn3e9h3sdfa'

อย่าลืมว่าต้องสร้าง nonces สำหรับทุกๆ คำขอหน้าเว็บและต้องคาดเดาไม่ได้

แฮชจะทำงานในลักษณะเดียวกัน แทนที่จะเพิ่มโค้ดลงในแท็กสคริปต์ ให้สร้างแฮช SHA ของสคริปต์แล้วเพิ่มลงในคำสั่ง script-src เช่น สมมติว่าหน้าเว็บมีเนื้อหาต่อไปนี้

<script>
  alert('Hello, world.');
</script>

นโยบายของคุณจะประกอบด้วย:

Content-Security-Policy: script-src 'sha256-qznLcsROx4GACP2dm0UCKCzCG-HiZ1guq6ZZDob_Tng='

มี 2-3 อย่างที่ควรทราบที่นี่ คำนำหน้า sha*- จะระบุอัลกอริทึมที่สร้างแฮช ในตัวอย่างด้านบน ระบบใช้ sha256- นอกจากนี้ CSP ยังรองรับ sha384- และ sha512- ด้วย ขณะสร้างแฮช โปรดอย่ารวมแท็ก <script> นอกจากนี้ การใช้อักษรตัวพิมพ์ใหญ่และการเว้นวรรค รวมถึงช่องว่างนำหน้าหรือต่อท้าย

การค้นหาใน Google เกี่ยวกับการสร้างแฮช SHA จะนำคุณไปยังโซลูชันในภาษาต่างๆ เมื่อใช้ Chrome 40 ขึ้นไป คุณจะเปิดเครื่องมือสำหรับนักพัฒนาเว็บแล้วโหลดหน้าเว็บซ้ำได้ แท็บคอนโซลจะมีข้อความแสดงข้อผิดพลาดพร้อมแฮช sha256 ที่ถูกต้องสำหรับสคริปต์ในหน้าแต่ละรายการ

ประเมินด้วย

แม้ว่าผู้โจมตีจะแทรกสคริปต์โดยตรงไม่ได้ พวกเขาก็อาจหลอกให้แอปพลิเคชันแปลงข้อความเฉื่อยเป็น JavaScript ที่เป็นไฟล์ปฏิบัติการได้ และเรียกใช้งานในนามของตนได้ eval(), new Function() , setTimeout([string], ...) และ setInterval([string], ...) คือเวกเตอร์ทั้งหมดที่ข้อความที่แทรกเข้ามาอาจทำให้เกิดการดำเนินการที่เป็นอันตรายโดยไม่คาดคิด การตอบสนองเริ่มต้นของ CSP ต่อความเสี่ยงนี้คือการบล็อกเวกเตอร์เหล่านี้ทั้งหมด

ซึ่งมีผลกระทบมากกว่า 2-3 อย่างเกี่ยวกับวิธีสร้างแอปพลิเคชัน ดังนี้

  • คุณต้องแยกวิเคราะห์ JSON ผ่าน JSON.parse ในตัวแทนที่จะใช้ eval การดำเนินการของ JSON แบบดั้งเดิมมีให้ใช้งานในทุกเบราว์เซอร์ตั้งแต่ IE8 และมีความปลอดภัยมากที่สุด
  • เขียนการเรียก setTimeout หรือ setInterval ที่คุณกำลังทำอยู่ด้วยฟังก์ชันในบรรทัดใหม่แทนสตริง เช่น
setTimeout("document.querySelector('a').style.display = 'none';", 10);

ควรเขียนให้ดีขึ้นดังนี้:

setTimeout(function () {
  document.querySelector('a').style.display = 'none';
}, 10);
  • หลีกเลี่ยงการกำหนดเทมเพลตในหน้าขณะรันไทม์: ไลบรารีที่มีเทมเพลตจำนวนมากใช้ new Function() อย่างอิสระเพื่อให้สร้างเทมเพลตได้เร็วขึ้นในระหว่างรันไทม์ เป็นการใช้งานโปรแกรมแบบไดนามิกที่ดี แต่ก็มีความเสี่ยงที่จะประเมินข้อความที่เป็นอันตราย บางเฟรมเวิร์กรองรับ CSP อยู่แล้ว แต่กลับไปใช้โปรแกรมแยกวิเคราะห์ที่มีประสิทธิภาพแทนเมื่อไม่มี eval คำสั่ง ng-csp ของ AngularJS เป็นตัวอย่างที่ดี

อย่างไรก็ตาม ตัวเลือกที่ดีกว่าควรใช้ภาษาเทมเพลตที่มีฟังก์ชันการคอมไพล์ (ตัวอย่างเช่น แฮนเดิลบาร์มี) การคอมไพล์เทมเพลตล่วงหน้าจะทำให้ผู้ใช้ได้รับประสบการณ์การใช้งานที่เร็วขึ้นกว่าการใช้รันไทม์ที่เร็วที่สุด และยังปลอดภัยด้วย หาก eval และตัวแปรที่แปลงข้อความเป็น JavaScript เป็นสิ่งจำเป็นต่อแอปพลิเคชัน คุณจะเปิดใช้ได้โดยการเพิ่ม 'unsafe-eval' เป็นแหล่งที่มาที่อนุญาตในคำสั่ง script-src แต่เราไม่แนะนำให้ทำเช่นนี้ การแบนความสามารถในการเรียกใช้สตริงทำให้ผู้โจมตีเรียกใช้โค้ดที่ไม่ได้รับอนุญาตในเว็บไซต์ได้ยากขึ้นมาก

การรายงาน

ความสามารถของ CSP ในการบล็อกทรัพยากรที่ไม่น่าเชื่อถือฝั่งไคลเอ็นต์เป็นสิ่งที่ได้ผลอย่างยิ่งสำหรับผู้ใช้ แต่การส่งการแจ้งเตือนบางอย่างกลับไปยังเซิร์ฟเวอร์อาจเป็นประโยชน์มาก เพื่อให้คุณสามารถระบุและตรวจหาข้อบกพร่องใดๆ ที่ทำให้เกิดการแทรกที่เป็นอันตรายได้ตั้งแต่แรก ด้วยเหตุนี้ คุณสั่งให้เบราว์เซอร์POSTรายงานการละเมิดรูปแบบ JSON ไปยังตำแหน่งที่ระบุในคำสั่ง report-uri ได้

Content-Security-Policy: default-src 'self'; ...; report-uri /my_amazing_csp_report_parser;

รายงานเหล่านี้จะมีหน้าตาดังนี้

{
  "csp-report": {
    "document-uri": "http://example.org/page.html",
    "referrer": "http://evil.example.com/",
    "blocked-uri": "http://evil.example.com/evil.js",
    "violated-directive": "script-src 'self' https://apis.google.com",
    "original-policy": "script-src 'self' https://apis.google.com; report-uri http://example.org/my_amazing_csp_report_parser"
  }
}

ซึ่งประกอบด้วยข้อมูลจำนวนมากที่จะช่วยให้คุณติดตามสาเหตุที่เฉพาะเจาะจงของการละเมิดได้ รวมถึงหน้าที่เกิดการละเมิด (document-uri), ผู้อ้างอิงของหน้าดังกล่าว (โปรดทราบว่าต่างจากช่องส่วนหัว HTTP ตรงที่คีย์ไม่ได้สะกดผิด), ทรัพยากรที่ละเมิดนโยบายของหน้าเว็บ (blocked-uri), คำสั่งเฉพาะเจาะจงที่ละเมิด (violated-directive) และนโยบายที่สมบูรณ์ของหน้าเว็บ (original-policy)

รายงานเท่านั้น

หากคุณเพิ่งเริ่มต้นกับ CSP คุณควรประเมินสถานะปัจจุบันของแอปพลิเคชันก่อนที่จะใช้นโยบายจริงกับผู้ใช้ เพื่อให้การทำให้ใช้งานได้เสร็จสมบูรณ์ คุณสามารถขอให้เบราว์เซอร์ตรวจสอบนโยบาย รายงานการละเมิด แต่ไม่บังคับใช้ข้อจำกัด ส่งส่วนหัว Content-Security-Policy-Report-Only แทนการส่งส่วนหัว Content-Security-Policy

Content-Security-Policy-Report-Only: default-src 'self'; ...; report-uri /my_amazing_csp_report_parser;

โดยนโยบายที่ระบุในโหมดรายงานเท่านั้นจะไม่บล็อกทรัพยากรที่ถูกจำกัด แต่จะส่งรายงานการละเมิดไปยังตำแหน่งที่คุณระบุ คุณจะส่งส่วนหัวทั้ง 2 รายการและบังคับใช้นโยบายใดนโยบายหนึ่งไปพร้อมๆ กับตรวจสอบอีกนโยบายหนึ่งได้ นี่เป็นวิธีที่ยอดเยี่ยมในการประเมินผลกระทบของการเปลี่ยนแปลง CSP ของแอปพลิเคชัน นั่นคือ การเปิดการรายงานสำหรับนโยบายใหม่ ตรวจสอบรายงานการละเมิด และแก้ไขข้อบกพร่องใดๆ ที่เกิดขึ้น เมื่อคุณพอใจกับผลของการเปลี่ยนแปลงแล้ว ให้เริ่มบังคับใช้นโยบายใหม่

การใช้งานในชีวิตจริง

CSP 1 ค่อนข้างใช้งานได้ใน Chrome, Safari และ Firefox แต่ IE 10 รองรับได้อย่างจำกัดมาก คุณดูข้อมูลที่เฉพาะเจาะจงได้ที่ caniuse.com CSP ระดับ 2 พร้อมใช้งานใน Chrome ตั้งแต่เวอร์ชัน 40 เว็บไซต์ขนาดใหญ่อย่าง Twitter และ Facebook ได้นำส่วนหัวนี้ไปใช้ (กรณีศึกษาของ Twitter เป็นสิ่งที่คุ้มค่าแก่การอ่าน) และมาตรฐานนี้ก็พร้อมช่วยให้คุณเริ่มนำไปใช้ในเว็บไซต์ของคุณเองได้

ขั้นตอนแรกในการสร้างนโยบายสำหรับแอปพลิเคชันคือการประเมินทรัพยากรที่คุณกำลังโหลดจริง เมื่อคุณคิดว่าเข้าใจวิธีการรวบรวมสิ่งต่างๆ ในแอปแล้ว ให้ตั้งค่านโยบายตามข้อกำหนดเหล่านั้น ลองมาดู Use Case ทั่วไป 2-3 อย่างเพื่อหาวิธีที่เราจะช่วยเหลือกรณีที่อยู่ในขอบเขตการป้องกันของ CSP ได้ดีที่สุด

กรณีการใช้งานที่ 1: วิดเจ็ตโซเชียลมีเดีย

  • ปุ่ม +1 ของ Google มีสคริปต์จาก https://apis.google.com และฝัง <iframe> จาก https://plusone.google.com คุณต้องมีนโยบายที่มีต้นทางทั้ง 2 รายการนี้จึงจะฝังปุ่มได้ นโยบายขั้นต่ำคือ script-src https://apis.google.com; child-src https://plusone.google.com นอกจากนี้ คุณยังต้องตรวจสอบว่าได้ดึงข้อมูลโค้ด JavaScript ที่ Google ให้มาไว้ในไฟล์ JavaScript ภายนอกแล้ว หากคุณมีนโยบายระดับ 1 ที่ใช้ frame-src ระดับ 2 กำหนดให้คุณต้องเปลี่ยนเป็น child-src สิ่งนี้ไม่จำเป็นใน CSP ระดับ 3 อีกต่อไป

  • ปุ่มชอบของ Facebook มีตัวเลือกการใช้งานมากมาย เราขอแนะนําให้ใช้เวอร์ชัน <iframe> ต่อไป เนื่องจากเวอร์ชันนี้ได้รับการแซนด์บ็อกซ์อย่างปลอดภัยจากส่วนที่เหลือของเว็บไซต์ และต้องใช้คำสั่ง child-src https://facebook.com เพื่อให้ทำงานได้อย่างถูกต้อง โปรดทราบว่าโดยค่าเริ่มต้น รหัส <iframe> ที่ Facebook ให้ไว้จะโหลด URL แบบสัมพัทธ์ //facebook.com เปลี่ยนเป็นระบุ HTTPS อย่างชัดแจ้ง: https://facebook.com ไม่มีเหตุผลที่จะใช้ HTTP หากคุณไม่จำเป็น

  • ปุ่มทวีตของ Twitter ต้องใช้การเข้าถึงสคริปต์และเฟรม โดยทั้ง 2 อย่างโฮสต์อยู่ที่ https://platform.twitter.com (ในทำนองเดียวกัน Twitter จะให้ URL สัมพัทธ์โดยค่าเริ่มต้น แก้ไขโค้ดเพื่อระบุ HTTPS เมื่อคัดลอก/วางในเครื่อง) คุณจะใช้ script-src https://platform.twitter.com; child-src https://platform.twitter.com ได้ตราบใดที่ย้ายข้อมูลโค้ด JavaScript ที่ Twitter ให้ไปไว้ในไฟล์ JavaScript ภายนอก

  • แพลตฟอร์มอื่นๆ มีข้อกำหนดที่คล้ายกันและแก้ไขในลักษณะเดียวกันได้ เราขอแนะนำให้ตั้งค่า default-src เป็น 'none' และดูคอนโซลเพื่อกำหนดทรัพยากรที่คุณต้องเปิดใช้เพื่อให้วิดเจ็ตทำงานได้

การรวมวิดเจ็ตหลายรายการนั้นทำได้ง่าย เพียงรวมคำสั่งของนโยบาย เช่น รวมทรัพยากรทั้งหมดประเภทเดียวไว้ในคำสั่งเดียว หากคุณต้องการวิดเจ็ตโซเชียลมีเดียทั้ง 3 แบบ นโยบายจะมีลักษณะดังนี้

script-src https://apis.google.com https://platform.twitter.com; child-src https://plusone.google.com https://facebook.com https://platform.twitter.com

กรณีการใช้งานที่ 2: การปิดล็อก

สมมติว่าคุณมีเว็บไซต์ธนาคารและต้องการตรวจสอบว่า มีทรัพยากรที่คุณเขียนขึ้นเองเท่านั้นที่สามารถโหลดได้ ในสถานการณ์นี้ ให้เริ่มต้นด้วยนโยบายเริ่มต้นที่บล็อกทุกสิ่งทุกอย่าง (default-src 'none') และสร้างจากที่นั่น

สมมติว่าธนาคารโหลดรูปภาพ รูปแบบ และสคริปต์ทั้งหมดจาก CDN ที่ https://cdn.mybank.net และเชื่อมต่อผ่าน XHR ไปยัง https://api.mybank.com/ เพื่อดึงข้อมูลต่างๆ ลงมา เฟรมจะใช้สำหรับเฉพาะหน้าเว็บที่เชื่อมโยงกับเว็บไซต์เท่านั้น (ไม่มีต้นทางของบุคคลที่สาม) ในเว็บไซต์ไม่มี Flash ไม่มีแบบอักษร ไม่ใช้องค์ประกอบเพิ่มเติม ส่วนหัว CSP ที่จำกัดที่สุดที่เราสามารถส่งได้คือ

Content-Security-Policy: default-src 'none'; script-src https://cdn.mybank.net; style-src https://cdn.mybank.net; img-src https://cdn.mybank.net; connect-src https://api.mybank.com; child-src 'self'

กรณีการใช้งานที่ 3: SSL เท่านั้น

ผู้ดูแลระบบฟอรัมอภิปรายงานแต่งงานต้องการดูแลให้ทรัพยากรทั้งหมดโหลดผ่านช่องทางที่ปลอดภัยเท่านั้น แต่ไม่ได้เขียนโค้ดมากนัก การเขียนซอฟต์แวร์ฟอรัมของบุคคลที่สามจำนวนมากให้มีสคริปต์และสไตล์แบบอินไลน์เป็นสิ่งที่เกินความสามารถของเขา นโยบายต่อไปนี้จะมีผล

Content-Security-Policy: default-src https:; script-src https: 'unsafe-inline'; style-src https: 'unsafe-inline'

แม้ว่าจะระบุ https: ใน default-src แต่สคริปต์และรูปแบบคำสั่งจะไม่รับค่าแหล่งที่มานั้นโดยอัตโนมัติ คำสั่งแต่ละรายการจะเขียนทับค่าเริ่มต้นสำหรับประเภททรัพยากรที่ระบุโดยสมบูรณ์

อนาคต

นโยบายรักษาความปลอดภัยเนื้อหาระดับ 2 เป็น คำแนะนำของผู้สมัคร คณะทำงานด้านการรักษาความปลอดภัยสำหรับเว็บแอปพลิเคชันของ W3C ได้เริ่มดำเนินการปรับปรุงนโยบายรักษาความปลอดภัยเนื้อหาระดับที่ 3 ของข้อกำหนดใหม่แล้ว

หากคุณสนใจในการพูดคุยเกี่ยวกับคุณลักษณะที่กำลังจะมีขึ้นเหล่านี้ ดูข้อมูลที่เก็บรายชื่ออีเมลแบบสาธารณะ-webappsec@ หรือเข้าร่วมด้วยตนเอง

ความคิดเห็น