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

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

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

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

TL;DR

  • ใช้รายการที่อนุญาตเพื่อแจ้งให้ลูกค้าทราบถึงสิ่งที่อนุญาตและไม่อนุญาต
  • ดูว่าคำสั่งใดบ้างที่ใช้ได้
  • ดูคีย์เวิร์ดที่ผู้ใช้ค้นหา
  • โค้ดแบบแทรกในบรรทัดและ 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 ว่าจะนำส่งโค้ดที่ถูกต้อง และเราไว้วางใจตัวเองว่าจะทําเช่นนั้นด้วย เราจึงขอกำหนดนโยบายที่อนุญาตให้สคริปต์ทำงานได้ก็ต่อเมื่อมาจากแหล่งที่มาอย่างใดอย่างหนึ่งต่อไปนี้

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 ที่อาจโหลดเป็นเวิร์กเกอร์ เวิร์กเกอร์ที่แชร์ หรือเวิร์กเกอร์บริการ ในเดือนกรกฎาคม 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 โดยคั่นคำสั่งด้วยเครื่องหมายอัฒภาค ตรวจสอบว่าคุณระบุแหล่งข้อมูลที่จําเป็นทั้งหมดของประเภทที่เฉพาะเจาะจงในคําแนะนํารายการเดียว หากคุณเขียนข้อมูลอย่าง 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" (และไม่ใช่จากโฮสต์ปัจจุบัน) ซึ่งอาจไม่ใช่สิ่งที่คุณตั้งใจ

การทำแซนด์บ็อกซ์

คำสั่งอีกรายการหนึ่งที่ควรพูดถึงคือ 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 ไปยังไฟล์ภายนอก และแทนที่ URL ของ javascript: และ <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 หรือไม่ก็ตาม Inline JavaScript จะผสมโครงสร้างและลักษณะการทำงานในลักษณะที่คุณไม่ควรทำ เบราว์เซอร์แคชทรัพยากรภายนอกได้ง่ายขึ้น นักพัฒนาซอฟต์แวร์เข้าใจได้ง่ายขึ้น และเอื้อต่อการคอมไพล์และการทำให้ไฟล์มีขนาดเล็ก คุณจะเขียนโค้ดได้ดีขึ้นหากย้ายโค้ดไปยังแหล่งข้อมูลภายนอก

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

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

ในกรณีที่จำเป็นต้องใช้

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

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

<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'

โปรดทราบว่าต้องสร้าง Nonce ใหม่สำหรับคำขอหน้าเว็บแต่ละรายการ และ Nonce ดังกล่าวต้องเดาไม่ได้

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

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

นโยบายของคุณควรมีข้อมูลต่อไปนี้

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

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

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

ประเมินผลด้วย

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

ซึ่งส่งผลต่อวิธีสร้างแอปพลิเคชันเป็นอย่างมาก

  • คุณต้องแยกวิเคราะห์ 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 เป็นตัวอย่างที่ดีในเรื่องนี้

อย่างไรก็ตาม ตัวเลือกที่ดีกว่าคือภาษาเทมเพลตที่เสนอการคอมไพล์ล่วงหน้า (เช่น Handlebars) การคอมไพล์เทมเพลตล่วงหน้าจะช่วยให้ผู้ใช้ได้รับประสบการณ์การใช้งานที่เร็วกว่าการใช้งานรันไทม์ที่เร็วที่สุด และปลอดภัยกว่าด้วย หาก eval และบรรทัดคำสั่งที่แปลงข้อความเป็น JavaScript ของ eval มีความสำคัญต่อแอปพลิเคชันของคุณ คุณสามารถเปิดใช้ได้โดยเพิ่ม '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 เป็นสิ่งที่ควรอ่าน) และมาตรฐานนี้พร้อมแล้วสําหรับให้คุณเริ่มใช้งานในเว็บไซต์ของคุณเอง

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

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

  • ปุ่ม +1 ของ Google มีสคริปต์จาก https://apis.google.com และฝัง <iframe> จาก https://plusone.google.com คุณต้องมีนโยบายที่มีทั้งต้นทางเหล่านี้จึงจะฝังปุ่มได้ นโยบายขั้นต่ำคือ 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

หากสนใจการพูดคุยเกี่ยวกับฟีเจอร์ที่กำลังจะเปิดตัวเหล่านี้ โปรดอ่านที่เก็บอีเมล public-webappsec@ หรือเข้าร่วมด้วยตัวเอง

ความคิดเห็น