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