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

ไมค์ เวสต์
โจ เมดเลย์
โจ้ เมดเลย์

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

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

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

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

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

หากคุณจำเป็นจริงๆ

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

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

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

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

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

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

แฮชก็ทำงานในลักษณะเดียวกัน ให้สร้างแฮช 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(), 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) ผู้อ้างอิงของหน้านั้น (โปรดทราบว่าคีย์ไม่สะกดผิด ทรัพยากรที่ละเมิดนโยบายของหน้าเว็บ (blocked-uri) คำสั่งเฉพาะเจาะจงที่หน้าเว็บละเมิด (violated-directive) และนโยบายที่สมบูรณ์ของหน้า (original-policy)

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

หากคุณเพิ่งเริ่มต้นใช้ CSP คุณควรประเมินสถานะปัจจุบันของแอปพลิเคชันก่อนเริ่มใช้นโยบาย Dr.conian กับผู้ใช้ของคุณ เพื่อปูทางไปสู่การทำให้ใช้งานได้อย่างสมบูรณ์ คุณขอให้เบราว์เซอร์ตรวจสอบนโยบาย รายงานการละเมิด แต่ไม่บังคับใช้ข้อจำกัดต่างๆ ได้ ให้ส่งส่วนหัว 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 ควรค่าแก่การอ่าน) และมาตรฐานนี้ก็พร้อมให้คุณเริ่มติดตั้งใช้งานในเว็บไซต์ของตัวเองได้มาก

ขั้นตอนแรกในการกำหนดนโยบายสำหรับแอปพลิเคชันคือการประเมินทรัพยากรที่คุณโหลดอยู่จริงๆ เมื่อคุณคิดว่ามีวิธีจัดการสิ่งต่างๆ ในแอปแล้ว ให้กำหนดนโยบายตามข้อกำหนดเหล่านั้น ลองดูกรณีการใช้งานที่พบบ่อย 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@ หรือเข้าร่วมด้วยตัวเอง

ความคิดเห็น